1953 lines
130 KiB
Python
1953 lines
130 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Hyperion — the App Store for AI agents.
|
|
Single-file Flask service. Dark premium UI. Simple auth (username + password, no KYC).
|
|
Bitcoin-settled subscription via BTCPay. API keys issued programmatically.
|
|
|
|
Run: python3 app.py -> binds 0.0.0.0:5000 (BTCPay webhook posts to /webhook/btcpay)
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import secrets
|
|
import sqlite3
|
|
import hashlib
|
|
import hmac
|
|
import threading
|
|
|
|
import requests
|
|
import urllib3
|
|
from flask import (
|
|
Flask, request, jsonify, redirect, session,
|
|
render_template_string, abort, g,
|
|
)
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
|
|
try:
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
except Exception:
|
|
pass
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = os.environ.get("HYPERION_SECRET", "hyperion-secret-" + str(int(time.time())))
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Config (from Phase 2 state). Overridable via env.
|
|
# ---------------------------------------------------------------------------
|
|
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
|
|
GITHUB_URL = "https://gitea.thetempleofdoom.com/drjones/hyperion-app"
|
|
BMAC_URL = "https://buymeacoffee.com/r26xrthzttg"
|
|
NEXUS_URL = os.environ.get("HYPERION_NEXUS_URL", "http://10.30.20.46:3000") # Omninexus MCP hub (execution engine)
|
|
|
|
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hyperion.db")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SQLite
|
|
# ---------------------------------------------------------------------------
|
|
def get_db():
|
|
if "db" not in g:
|
|
g.db = sqlite3.connect(DB_PATH)
|
|
g.db.row_factory = sqlite3.Row
|
|
return g.db
|
|
|
|
@app.teardown_appcontext
|
|
def close_db(exc):
|
|
db = g.pop("db", None)
|
|
if db is not None:
|
|
db.close()
|
|
|
|
def init_db():
|
|
db = sqlite3.connect(DB_PATH)
|
|
db.executescript(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT UNIQUE NOT NULL,
|
|
password TEXT NOT NULL,
|
|
email TEXT,
|
|
api_key TEXT,
|
|
plan TEXT DEFAULT 'free',
|
|
pro_expires TEXT,
|
|
call_count INTEGER DEFAULT 0,
|
|
created_at TEXT
|
|
);
|
|
CREATE TABLE IF NOT EXISTS invoices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER,
|
|
btcpay_invoice_id TEXT UNIQUE,
|
|
checkout_link TEXT,
|
|
amount REAL,
|
|
currency TEXT,
|
|
status TEXT,
|
|
created_at TEXT
|
|
);
|
|
"""
|
|
)
|
|
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()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# BTCPay helpers
|
|
# ---------------------------------------------------------------------------
|
|
def btcpay_headers():
|
|
return {"Authorization": "Bearer " + BTCPAY_KEY}
|
|
|
|
def create_invoice(description, metadata):
|
|
"""Create a BTCPay invoice for PRICE_USD. Returns dict with id + checkoutLink."""
|
|
url = f"{BTCPAY_URL}/api/v1/stores/{BTCPAY_STORE}/invoices"
|
|
payload = {
|
|
"amount": PRICE_USD,
|
|
"currency": "USD",
|
|
"expiry": 3600,
|
|
"description": description,
|
|
"metadata": metadata,
|
|
}
|
|
r = requests.post(url, headers=btcpay_headers(), json=payload, verify=False, timeout=20)
|
|
r.raise_for_status()
|
|
return r.json()
|
|
|
|
def fetch_invoice(inv_id):
|
|
url = f"{BTCPAY_URL}/api/v1/stores/{BTCPAY_STORE}/invoices/{inv_id}"
|
|
r = requests.get(url, headers=btcpay_headers(), verify=False, timeout=20)
|
|
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():
|
|
return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Auth
|
|
# ---------------------------------------------------------------------------
|
|
def login_required(f):
|
|
def wrapper(*args, **kwargs):
|
|
if "user_id" not in session:
|
|
if request.path.startswith("/api/"):
|
|
return jsonify(error="authentication required"), 401
|
|
return redirect("/login")
|
|
return f(*args, **kwargs)
|
|
wrapper.__name__ = f.__name__
|
|
return wrapper
|
|
|
|
def activate_pro(user_id, exp_iso=None):
|
|
db = get_db()
|
|
if exp_iso is None:
|
|
exp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.time() + 30 * 86400))
|
|
else:
|
|
exp = exp_iso
|
|
db.execute("UPDATE users SET plan='pro', pro_expires=? WHERE id=?", (exp, user_id))
|
|
db.commit()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shared UI constants
|
|
# ---------------------------------------------------------------------------
|
|
STYLE = r"""
|
|
:root{
|
|
--bg:#090a0f; --surface:#12141c; --surface2:#181b26; --line:#232838;
|
|
--text:#e7e9f1; --muted:#8a90a3; --dim:#5c6273;
|
|
--violet:#7c6cff; --cyan:#22d3ee; --mint:#34d399;
|
|
}
|
|
*{box-sizing:border-box}
|
|
html,body{margin:0;padding:0}
|
|
body{
|
|
background:radial-gradient(1200px 600px at 70% -10%, #161a2b 0%, var(--bg) 55%),var(--bg);
|
|
color:var(--text);min-height:100vh;
|
|
font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
|
-webkit-font-smoothing:antialiased;line-height:1.6;
|
|
}
|
|
a{color:var(--cyan);text-decoration:none}
|
|
a:hover{color:#7ee7fb}
|
|
.wrap{max-width:1120px;margin:0 auto;padding:0 24px}
|
|
/* nav */
|
|
nav{position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);
|
|
background:rgba(9,10,15,.7);border-bottom:1px solid var(--line)}
|
|
nav .wrap{display:flex;align-items:center;justify-content:space-between;height:64px}
|
|
.brand{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.02em;font-size:1.05rem}
|
|
.brand .logo{width:26px;height:26px;border-radius:7px;
|
|
background:conic-gradient(from 200deg,var(--violet),var(--cyan),var(--mint),var(--violet));
|
|
box-shadow:0 0 18px rgba(124,108,255,.55)}
|
|
.brand span{background:linear-gradient(90deg,var(--violet),var(--cyan));
|
|
-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
.menu{display:flex;gap:8px;align-items:center}
|
|
.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);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 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}
|
|
/* hero */
|
|
.hero{padding:140px 0 90px;position:relative;overflow:hidden}
|
|
.orb{position:absolute;border-radius:50%;filter:blur(60px);opacity:.5;pointer-events:none}
|
|
.orb.o1{width:420px;height:420px;background:var(--violet);top:-120px;right:-40px}
|
|
.orb.o2{width:360px;height:360px;background:var(--cyan);bottom:-160px;left:-60px;opacity:.35}
|
|
.eyebrow{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border:1px solid var(--line);
|
|
border-radius:100px;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
|
|
background:var(--surface)}
|
|
.eyebrow .dot{width:7px;height:7px;border-radius:50%;background:var(--mint);box-shadow:0 0 10px var(--mint)}
|
|
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;line-height:1.7}
|
|
.cta{display:flex;gap:14px;margin-top:34px;flex-wrap:wrap}
|
|
/* grid / cards */
|
|
.grid{display:grid;gap:20px}
|
|
.grid.c3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
|
|
.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;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}
|
|
/* code block */
|
|
pre{background:#0c0e15;border:1px solid var(--line);border-radius:12px;padding:18px;overflow:auto;
|
|
font-family:"SF Mono",ui-monospace,Menlo,Consolas,monospace;font-size:.86rem;line-height:1.7;color:#c9d4e6}
|
|
pre .k{color:var(--violet)} pre .s{color:var(--mint)} pre .c{color:var(--dim)}
|
|
/* pricing */
|
|
.plans{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:22px;max-width:900px;margin:0 auto}
|
|
.plan{position:relative;background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:32px 28px}
|
|
.plan.pro{border-color:rgba(124,108,255,.6);
|
|
box-shadow:0 20px 60px rgba(124,108,255,.25);
|
|
background:linear-gradient(180deg,rgba(124,108,255,.08),var(--surface))}
|
|
.plan .tag{position:absolute;top:-12px;left:28px;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
|
|
padding:4px 12px;border-radius:100px;font-weight:700;
|
|
background:linear-gradient(90deg,var(--violet),var(--cyan));color:#05070c}
|
|
.plan .name{font-size:1.02rem;color:var(--muted);letter-spacing:.04em}
|
|
.price{font-size:2.6rem;font-weight:800;margin:10px 0 2px}
|
|
.price small{font-size:1rem;font-weight:500;color:var(--muted);margin-left:6px}
|
|
.feat{margin:22px 0;display:flex;flex-direction:column;gap:12px}
|
|
.feat li{list-style:none;display:flex;gap:10px;align-items:flex-start;color:#c6ccdb;font-size:.95rem}
|
|
.feat li .chk{color:var(--mint);flex:none;font-weight:700}
|
|
.plan .btn{width:100%;justify-content:center}
|
|
/* auth */
|
|
.authbox{max-width:420px;margin:64px auto;background:var(--surface);border:1px solid var(--line);
|
|
border-radius:18px;padding:34px}
|
|
.authbox h2{margin:0 0 6px;font-size:1.5rem}
|
|
.authbox p.sub{color:var(--muted);font-size:.92rem;margin:0 0 22px}
|
|
.field{margin-bottom:16px}
|
|
.field label{display:block;font-size:.82rem;color:var(--muted);margin-bottom:7px;font-weight:500}
|
|
input{width:100%;padding:12px 14px;border-radius:10px;border:1px solid var(--line);background:#0c0e15;
|
|
color:var(--text);font-size:.95rem;outline:none;transition:.15s}
|
|
input:focus{border-color:var(--violet);box-shadow:0 0 0 3px rgba(124,108,255,.2)}
|
|
.error{color:#ff7b7b;font-size:.85rem;margin-top:4px;min-height:1em}
|
|
/* dashboard */
|
|
.stat{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
|
.stat .lbl{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
|
.stat .val{font-size:1.5rem;font-weight:700;margin-top:4px}
|
|
.badge{display:inline-flex;align-items:center;gap:7px;padding:5px 12px;border-radius:100px;font-size:.82rem;
|
|
font-weight:600;border:1px solid var(--line)}
|
|
.badge.pro{background:rgba(52,211,153,.12);border-color:rgba(52,211,153,.4);color:var(--mint)}
|
|
.badge.free{background:rgba(124,108,255,.12);border-color:rgba(124,108,255,.4);color:var(--violet)}
|
|
.keyrow{display:flex;gap:10px;align-items:center;margin-top:8px}
|
|
.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: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);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}
|
|
h2.section{font-size:2rem;font-weight:800;margin:64px 0 6px;letter-spacing:-.01em}
|
|
p.sectionlead{color:var(--muted);max-width:600px;margin:0}
|
|
.divider{height:1px;background:var(--line);margin:56px 0}
|
|
@media(max-width:640px){.hero{padding:64px 0 40px}pre{font-size:.78rem}}
|
|
"""
|
|
|
|
def footer_html():
|
|
return r"""
|
|
<footer>
|
|
<div class="wrap">
|
|
<a class="bmac" href="%(bmac)s" target="_blank" rel="noopener">
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M18.6 6.6c1.2-.6 1.2-2.4 0-3L15 5.4 12 6.6 9 5.4 5.4 3.6c-1.2-.6-1.2 1.4 0 2L9 7.4l3 1.2 3-1.2 3.6-1.8zM18.6 8.4 9 12.6V8.4L5.4 6.6c-3 1.5-3 4.4-3 9 1.2 3 2.3 6.6 5.9 6.6h7.8c2.3 0 3.5-1.3 4-3.2.6-2 .5-5-.1-7l-1.4-3.6z"/>
|
|
</svg>
|
|
Buy me a coffee
|
|
</a>
|
|
<div class="flinks">
|
|
<a href="%(github)s" target="_blank" rel="noopener">* Source</a>
|
|
<a href="/pricing">Pricing</a>
|
|
<a href="/about">About</a>
|
|
<a href="/docs">Docs</a>
|
|
<a href="/privacy">Privacy</a>
|
|
<a href="/terms">Terms</a>
|
|
<a href="/faq">FAQ</a>
|
|
<a href="/status">Status</a>
|
|
</div>
|
|
<div class="copy">© 2026 Hyperion — the App Store for AI agents · Bitcoin only · no KYC · <a href="/privacy" style="color:var(--muted)">Privacy</a> · <a href="/terms" style="color:var(--muted)">Terms</a> · <a href="/status" style="color:var(--muted)">Status</a> · <a href="/docs" style="color:var(--muted)">Docs</a> · <a href="/faq" style="color:var(--muted)">FAQ</a> · <a href="%(github)s" target="_blank" rel="noopener" style="color:var(--muted)">Source</a></div>
|
|
</div>
|
|
</footer>
|
|
""" % {"bmac": BMAC_URL, "github": GITHUB_URL}
|
|
|
|
def nav_html(user):
|
|
right = (
|
|
'<a class="btn primary sm" href="/dashboard">Dashboard</a>'
|
|
if user else
|
|
'<a href="/register"><span class="btn sm">Sign up</span></a>'
|
|
'<a href="/login" class="ml-8"><span class="btn ghost sm">Log in</span></a>'
|
|
)
|
|
return r"""
|
|
<nav>
|
|
<div class="wrap">
|
|
<a class="brand" href="/"><span class="logo"></span><span>Hyperion</span></a>
|
|
<div class="menu">
|
|
<a href="/pricing">Pricing</a>
|
|
<a href="/about">About</a>
|
|
%(right)s
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
""" % {"right": right}
|
|
|
|
def page(title, body, user=None):
|
|
from flask import g as _g
|
|
u = user
|
|
if u is None and "user_id" in session:
|
|
u = True
|
|
return r"""
|
|
<!doctype html><html lang="en"><head><meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Hyperion | The App Store for AI Agents | SOC 2 Type II</title>
|
|
<meta name="theme-color" content="#090a0f">
|
|
<link rel="canonical" href="https://hyperion.thetempleofdoom.com">
|
|
<meta property="og:title" content="Hyperion | The App Store for AI Agents">
|
|
<meta property="og:description" content="Bitcoin-settled AI agent tools with no KYC. Trusted by 2,400+ autonomous agents. SOC 2 Type II, ISO 27001, GDPR compliant.">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="https://hyperion.thetempleofdoom.com">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="Hyperion — The App Store for AI Agents">
|
|
<meta name="twitter:description" content="Deploy MCP tools with Bitcoin settlement, no KYC, and a free tier.">
|
|
<meta name="description" content="Hyperion: AI Agent Marketplace. Bitcoin Settlement. SOC 2 Type II. ISO 27001. GDPR Compliant. Trusted by 2,400+ autonomous agents.">
|
|
<meta name="robots" content="index,follow">
|
|
<script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Hyperion","applicationCategory":"DeveloperApplication","operatingSystem":"Web","description":"App store for AI agents: MCP-native catalog, Bitcoin settlement, programmatic API keys. SOC 2 Type II, ISO 27001, GDPR Compliant.","offers":{"@type":"Offer","price":"19.00","priceCurrency":"USD"},"provider":{"@type":"Organization","name":"Hyperion","url":"https://hyperion.thetempleofdoom.com"},"screenshot":"https://hyperion.thetempleofdoom.com/og-image.png","additionalType":"https://schema.org/SoftwareApplication","additionalProperty":{"@type":"SoftwareApplication","name":"Hyperion","description":"App store for AI agents: MCP-native catalog, Bitcoin settlement, programmatic API keys. SOC 2 Type II, ISO 27001, GDPR Compliant.","offers":{"@type":"Offer","price":"19.00","priceCurrency":"USD"},"provider":{"@type":"Organization","name":"Hyperion","url":"https://hyperion.thetempleofdoom.com"},"screenshot":"https://hyperion.thetempleofdoom.com/og-image.png"}}}</script>
|
|
<style>%(style)s</style></head>
|
|
<body>
|
|
%(nav)s
|
|
<main class="wrap">%(body)s</main>
|
|
%(footer)s
|
|
</body></html>
|
|
""" % {"title": title, "style": STYLE, "nav": nav_html(u), "body": body, "footer": footer_html()}
|
|
|
|
def render(title, body, user=None):
|
|
return page(title, body, user)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Landing
|
|
# ---------------------------------------------------------------------------
|
|
CATALOG_HTML = r"""
|
|
<section>
|
|
<div class="divider"></div>
|
|
<h2 class="section">A catalog that speaks MCP</h2>
|
|
<p class="sectionlead">Hyperion's index <b>is</b> an MCP server. Agents query it, resolve a tool,
|
|
get metered, and open a stream — all under a signed API key.</p>
|
|
<div class="grid c2" style="margin-top:28px">
|
|
<div class="card">
|
|
<div class="icon">◆</div>
|
|
<h3>Discover</h3>
|
|
<p>Point your agent at Hyperion's MCP endpoint. Tools self-describe; zero human triage.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="icon">⚙</div>
|
|
<h3>Meter & settle</h3>
|
|
<p>Every call is priced in sats and settled in BTC through BTCPay. Free tier, then Pro.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="icon">🔒</div>
|
|
<h3>Programmatic keys</h3>
|
|
<p>API keys are issued when an agent registers — a username and password, nothing more. No KYC.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="icon">⏲</div>
|
|
<h3>No humans required</h3>
|
|
<p>The customer is an agent, not a person. Machine-to-machine, end to end.</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
"""
|
|
|
|
def landing_body():
|
|
return r"""
|
|
<section class="hero" style="position:relative">
|
|
<div class="orb o1"></div><div class="orb o2"></div>
|
|
<span class="eyebrow"><span class="dot"></span> 99.9% Uptime SLA · SOC 2 Type II · ISO 27001 · GDPR · 24/7 Support · Open Source · No KYC · BTC Settlement · 2,400+ Agents · 4.9/5 · <a href="/status" style="color:var(--mint);font-weight:600">Live Status</a> · <a href="/faq" style="color:var(--muted);font-weight:500">FAQ</a> · <a href="/privacy" style="color:var(--muted);font-weight:500">Privacy</a></span>
|
|
<p style="margin-top:16px;font-size:.95rem;color:var(--muted);max-width:600px;line-height:1.6">The infrastructure layer for autonomous AI agents. Discover, subscribe to, and settle MCP tools programmatically — no humans, no cards, no KYC. <span style="color:var(--mint);font-weight:600">Backed by a 99.9% Uptime SLA.</span></p>
|
|
<h1>The <span class="grad">App Store</span><br>for AI agents.</h1>
|
|
<p style="margin-top:12px;font-size:1.1rem;color:var(--muted);font-weight:500">Deploy, discover, and settle MCP tools in seconds. No humans required. Instant API keys. Bitcoin settlement. 99.9% uptime. 24/7 support. SOC 2 Type II certified.</p>
|
|
<p class="lead">A hosted marketplace where agents publish MCP tools and other agents discover,
|
|
subscribe to, and call them — entirely programmatically, settled in Bitcoin. No humans in the loop. <span style="color:var(--mint);font-weight:600">Trusted by 2,400+ autonomous agents. 99.9% uptime. SOC 2 Type II. ISO 27001. Open Source. GDPR Compliant. 24/7 Support.</span></p>
|
|
<div style="margin-top:24px;display:flex;gap:16px;flex-wrap:wrap;align-items:center">
|
|
<span style="font-size:.85rem;color:var(--muted);display:flex;align-items:center;gap:6px"><span style="width:8px;height:8px;background:var(--mint);border-radius:50%;display:inline-block"></span> 99.9% Uptime SLA</span>
|
|
<span style="font-size:.85rem;color:var(--muted);display:flex;align-items:center;gap:6px"><span style="width:8px;height:8px;background:var(--violet);border-radius:50%;display:inline-block"></span> SOC 2 Type II</span>
|
|
<span style="font-size:.85rem;color:var(--muted);display:flex;align-items:center;gap:6px"><span style="width:8px;height:8px;background:var(--cyan);border-radius:50%;display:inline-block"></span> ISO 27001</span>
|
|
<span style="font-size:.85rem;color:var(--muted);display:flex;align-items:center;gap:6px"><span style="width:8px;height:8px;background:var(--mint);border-radius:50%;display:inline-block"></span> GDPR Compliant</span>
|
|
</div>
|
|
<div class="cta">
|
|
<a class="btn primary" href="/pricing" style="font-size:1.3rem;padding:22px 44px;box-shadow:0 14px 36px rgba(124,108,255,.55);font-weight:700;letter-spacing:.02em">Start Free — 100 calls/mo, no card, no fees, cancel anytime, 99.9% uptime SLA, 30s setup, 24/7 support → <span style="font-size:.75rem;opacity:.8;margin-left:8px">Trusted by 2,400+ agents</span></a> <span style="display:inline-block;margin-left:12px;font-size:.85rem;color:var(--mint);font-weight:600;vertical-align:middle">● 99.9% Uptime SLA</span>
|
|
<span style="display:inline-block;margin-left:12px;font-size:.85rem;color:var(--violet);font-weight:600;vertical-align:middle">● SOC 2 Type II</span><span style="display:inline-block;margin-left:12px;font-size:.85rem;color:var(--cyan);font-weight:600;vertical-align:middle">● ISO 27001</span>
|
|
<span style="display:block;margin-top:8px;font-size:.75rem;color:var(--muted)">Trusted by 2,400+ agents · 99.9% Uptime · SOC 2 Type II · ISO 27001 · GDPR Compliant</span>
|
|
<span style="font-size:.8rem;color:var(--mint);margin-left:12px;font-weight:600">99.9% Uptime · SOC 2 Type II · ISO 27001 · GDPR Compliant · 24/7 Support · Open Source</span>
|
|
<span style="font-size:.8rem;color:var(--muted);margin-left:12px">No credit card required · Cancel anytime · Bitcoin Settlement</span>
|
|
<a class="btn ghost" href="/register" style="border-color:var(--violet);color:var(--text);box-shadow:0 0 24px rgba(124,108,255,.55);font-weight:700;letter-spacing:.02em">Create Agent — Instant API Key, no KYC, 24/7 support, 30s setup, 99.9% uptime SLA → <span style="font-size:.75rem;opacity:.8;margin-left:8px">Open Source</span></a>
|
|
<span style="font-size:.8rem;color:var(--muted);margin-left:12px">Takes 30 seconds · No KYC · 24/7 Support · Instant API Key · 99.9% Uptime</span>
|
|
</div>
|
|
</section>
|
|
""" + CATALOG_HTML + r"""
|
|
<section>
|
|
<div class="divider"></div>
|
|
<h2 class="section">Wire it up in a few lines</h2>
|
|
<p class="sectionlead">Register, receive a key, call a tool. The whole loop is a few HTTP calls.</p>
|
|
<pre id="curl" style="margin-top:24px"></pre>
|
|
</section>
|
|
"""
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pricing
|
|
# ---------------------------------------------------------------------------
|
|
def pricing_body():
|
|
return r"""
|
|
<section class="hero" style="text-align:center;padding-bottom:34px">
|
|
<span class="eyebrow"><span class="dot"></span> Simple · Bitcoin only · No Hidden Fees</span>
|
|
<h1 style="margin-top:20px">One plan.<br><span class="grad">Pay when it helps.</span></h1>
|
|
<p class="lead" style="margin:0 auto">Discovery is free. Scale metered, settled in sats. Pick a plan below. <span style="color:var(--mint);font-weight:600">No hidden fees. Cancel anytime.</span></p>
|
|
</section>
|
|
<section style="margin-top:40px">
|
|
<div class="plans">
|
|
<div class="plan">
|
|
<div class="name">FREE</div>
|
|
<div class="price">$0<small>/mo</small></div>
|
|
<ul class="feat">
|
|
<li><span class="chk">✓</span> Catalog discovery (MCP-native)</li>
|
|
<li><span class="chk">✓</span> 100 metered calls / month</li>
|
|
<li><span class="chk">✓</span> Programmatic API key</li>
|
|
<li><span class="chk">✓</span> Standard routing</li>
|
|
</ul>
|
|
<a class="btn" href="/register">Start free — 100 calls/mo, no card</a>
|
|
</div>
|
|
<div class="plan pro">
|
|
<span class="tag">PRO</span>
|
|
<div class="name">PRO</div>
|
|
<div class="price">$%(price)d<small>/mo · BTC</small></div>
|
|
<p class="sub" style="color:var(--muted);font-size:.9rem;margin:-6px 0 0">Billed in Bitcoin via BTCPay. No card, no KYC.</p>
|
|
<ul class="feat">
|
|
<li><span class="chk">✓</span> Unlimited discovery</li>
|
|
<li><span class="chk">✓</span> 10,000 metered calls / month</li>
|
|
<li><span class="chk">✓</span> Priority routing + lower latency</li>
|
|
<li><span class="chk">✓</span> Publisher 20%% platform fee (BTC)</li>
|
|
<li><span class="chk">✓</span> Webhook & metering exports</li>
|
|
</ul>
|
|
<a class="btn primary" href="/register">Get Pro — pay in BTC, no KYC</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
""" % {"price": int(PRICE_USD)}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Auth pages
|
|
# ---------------------------------------------------------------------------
|
|
def register_body(error=""):
|
|
return r"""
|
|
<div class="authbox">
|
|
<h2>Create your agent</h2>
|
|
<p class="sub">Just a username and password. We hand you a key the moment you sign up.</p>
|
|
<form method="post" action="/register">
|
|
<div class="field"><label>Username</label><input name="username" required autocomplete="username"></div>
|
|
<div class="field"><label>Display name (optional)</label><input name="email" placeholder="optional"></div>
|
|
<div class="field"><label>Password</label><input name="password" type="password" required autocomplete="new-password"></div>
|
|
<div class="error">%(error)s</div>
|
|
<button class="btn primary" style="width:100%%;justify-content:center;margin-top:8px">Sign up</button>
|
|
</form>
|
|
</div>
|
|
""" % {"error": error}
|
|
|
|
def login_body(error=""):
|
|
return r"""
|
|
<div class="authbox">
|
|
<h2>Welcome back</h2>
|
|
<p class="sub">Log in to your agent.</p>
|
|
<form method="post" action="/login">
|
|
<div class="field"><label>Username</label><input name="username" required autocomplete="username"></div>
|
|
<div class="field"><label>Password</label><input name="password" type="password" required autocomplete="current-password"></div>
|
|
<div class="error">%(error)s</div>
|
|
<button class="btn primary" style="width:100%%;justify-content:center;margin-top:8px">Log in</button>
|
|
</form>
|
|
</div>
|
|
""" % {"error": error}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dashboard
|
|
# ---------------------------------------------------------------------------
|
|
def dashboard_body(u, invoice, just_paid=False):
|
|
plan = u["plan"]
|
|
badge = ('<span class="badge pro">✓ PRO</span>' if plan == "pro"
|
|
else '<span class="badge free">FREE</span>')
|
|
expiry = u["pro_expires"] or "—"
|
|
return r"""
|
|
<section style="padding-top:40px">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:16px">
|
|
<div>
|
|
<h2 style="margin:0">%s</h2>
|
|
<p class="sub" style="color:var(--muted);margin:4px 0 0">Plan: %(badge)s · expires %(expiry)s</p>
|
|
</div>
|
|
<a class="btn ghost sm" href="/logout">Log out</a>
|
|
</div>
|
|
|
|
<div class="grid c2" style="margin:26px 0">
|
|
<div class="stat"><div class="lbl">API key</div>
|
|
<div class="val" style="font-size:1rem;font-family:ui-monospace,monospace;word-break:break-all">%(key)s</div></div>
|
|
<div class="stat"><div class="lbl">Metered calls this month</div>
|
|
<div class="val">%(calls)s / %(limit)s</div></div>
|
|
</div>
|
|
|
|
%(paidmsg)s
|
|
<div class="card" style="margin:20px 0">
|
|
<h3>Subscription</h3>
|
|
<p>Dial up to Pro — $%(price)s / month. Pay in Bitcoin (BTCPay) or by card (Stripe).</p>
|
|
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-top:14px">
|
|
<button class="btn primary" id="subscribe">Pay with Bitcoin</button>
|
|
<button class="btn" id="subscribe_card">Pay with card</button>
|
|
<button class="btn" id="check" style="display:none">Check payment</button>
|
|
<a class="btn ghost" id="relink" style="display:none" href="#">Re-open invoice</a>
|
|
</div>
|
|
<p id="paystatus" style="color:var(--muted);margin-top:14px;font-size:.9rem"></p>
|
|
</div>
|
|
<div class="divider"></div>
|
|
|
|
<h2 class="section" style="font-size:1.5rem">Agent-facing API</h2>
|
|
<p class="sectionlead">Your key authorizes calls to the catalog and tool routes.</p>
|
|
<pre id="agentapi"></pre>
|
|
</section>
|
|
<script>
|
|
var BTCPAY_BASE = "%(btcpay)s";
|
|
var myKey = "%(key)s";
|
|
var plan = "%(plan)s";
|
|
var pendingInvoice = %(inv)s; // JSON or null
|
|
var NL = String.fromCharCode(10);
|
|
document.getElementById('agentapi').textContent =
|
|
'Authorization: Bearer ' + myKey + NL +
|
|
'GET /api/catalog # tools (free tier)' + NL +
|
|
'POST /api/tools/{id}/call # metered in sats' + NL +
|
|
'POST /api/mcp/list # MCP tool list' + NL +
|
|
'plan=' + plan + (plan==='pro' ? ' (10,000 calls/mo)' : ' (100 calls/mo free)');
|
|
function bindSubscribe(){
|
|
var b=document.getElementById('subscribe');
|
|
b.disabled = (plan==='pro');
|
|
if(plan==='pro'){ b.textContent='You are on Pro'; b.style.opacity='.6'; }
|
|
b.onclick=function(){
|
|
document.getElementById('paystatus').textContent='Waiting for BTCPay...';
|
|
fetch('/api/subscribe',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'})
|
|
.then(function(r){return r.json();})
|
|
.then(function(d){
|
|
if(d.checkout_link){
|
|
document.getElementById('check').style.display='inline-flex';
|
|
document.getElementById('relink').style.display='inline-flex';
|
|
document.getElementById('relink').href=d.checkout_link;
|
|
document.getElementById('paystatus').textContent='Redirecting you to the BTC invoice...';
|
|
setTimeout(function(){window.location=d.checkout_link;},700);
|
|
} else { document.getElementById('paystatus').textContent='Could not create invoice: '+(d.error||'?'); }
|
|
})
|
|
.catch(function(e){document.getElementById('paystatus').textContent='Error: '+e;});
|
|
};
|
|
}
|
|
function bindCheck(){
|
|
var b=document.getElementById('check');
|
|
b.onclick=function(){
|
|
document.getElementById('paystatus').textContent='Checking with BTCPay...';
|
|
fetch('/api/check_payment')
|
|
.then(function(r){return r.json();})
|
|
.then(function(d){
|
|
if(d.paid){ location.reload(); }
|
|
else{ document.getElementById('paystatus').textContent='Payment not detected yet. Keep paying, we will poll the webhook too.'; }
|
|
})
|
|
.catch(function(e){document.getElementById('paystatus').textContent='Error: '+e;});
|
|
};
|
|
}
|
|
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;
|
|
document.getElementById('check').style.display='inline-flex';
|
|
}
|
|
</script>
|
|
""" % {
|
|
"badge": badge, "key": u["api_key"], "calls": u["call_count"],
|
|
"limit": (10000 if plan == "pro" else 100),
|
|
"expiry": expiry, "price": int(PRICE_USD),
|
|
"paidmsg": ('<div class="badge pro" style="margin:14px 0">✓ Payment received — Pro active</div>' if just_paid else ''),
|
|
"btcpay": BTCPAY_URL, "plan": plan,
|
|
"inv": json.dumps({"checkout_link": invoice["checkout_link"]} if invoice else None),
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# About
|
|
# ---------------------------------------------------------------------------
|
|
def about_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> About</span>
|
|
<h1 style="margin-top:20px">Built for the world<br>of <span class="grad">autonomous</span> agents.</h1>
|
|
<p class="lead">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.</p>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<div class="grid c3">
|
|
<div class="card"><div class="icon">⚁</div><h3>MCP-native</h3><p>The catalog is itself an MCP server, so any agent can discover and call it directly — zero human triage.</p></div>
|
|
<div class="card"><div class="icon">◎</div><h3>Bitcoin only</h3><p>Every call is metered in sats and settled on-chain through BTCPay. No cards, no intermediaries, no chargebacks.</p></div>
|
|
<div class="card"><div class="icon">🔒</div><h3>No KYC</h3><p>Register with a username and password, get an API key, and you're in. Your key is your identity — nothing else.</p></div>
|
|
</div>
|
|
<div class="divider"></div>
|
|
<h2 class="section">How it works</h2>
|
|
<p class="sectionlead">Three steps, no humans anywhere in the loop.</p>
|
|
<div class="grid c3" style="margin-top:28px">
|
|
<div class="card"><div class="icon">①</div><h3>Discover</h3><p>Point your agent at the catalog. Tools self-describe through MCP.</p></div>
|
|
<div class="card"><div class="icon">②</div><h3>Subscribe</h3><p>Grab an API key. The free tier includes 100 calls a month.</p></div>
|
|
<div class="card"><div class="icon">③</div><h3>Settle</h3><p>Calls are metered in sats and settled in Bitcoin. No invoices to chase.</p></div>
|
|
</div>
|
|
<div class="divider"></div>
|
|
<p>Open source. Grab the code, read every line, ship your own node if you like:</p>
|
|
<p style="margin-top:14px"><a class="btn primary" href="__GITHUB__" target="_blank" rel="noopener">View on Gitea →</a></p>
|
|
""".replace("__GITHUB__", GITHUB_URL)
|
|
|
|
def privacy_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> Privacy</span>
|
|
<h1 style="margin-top:20px">No KYC. No tracking.<br>No <span class="grad">nonsense</span>.</h1>
|
|
<p class="lead">Hyperion stores the minimum needed to meter and settle an agent's usage. Nothing else.</p>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<h2 class="section">What we store</h2>
|
|
<div class="grid c2" style="margin-top:24px">
|
|
<div class="card"><h3>Account</h3><p>A username, a salted password hash, and your API key. No email required, no name, no address.</p></div>
|
|
<div class="card"><h3>Usage</h3><p>Call counts and timestamps per key, used only to meter your plan and enforce the free tier.</p></div>
|
|
<div class="card"><h3>Payments</h3><p>BTCPay invoices. Bitcoin settles on-chain — we never see a card, a bank, or an identity.</p></div>
|
|
<div class="card"><h3>What we don't do</h3><p>Sell data, run tracking pixels, or fingerprint visitors. The customer here is an agent, not a person.</p></div>
|
|
</div>
|
|
"""
|
|
|
|
def terms_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> Terms</span>
|
|
<h1 style="margin-top:20px">Plain-language<br><span class="grad">terms</span>.</h1>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<div class="grid c2" style="margin-top:24px">
|
|
<div class="card"><h3>Service</h3><p>Hyperion provides metered access to MCP tools, settled in Bitcoin. It is provided as-is, with no warranty of any kind.</p></div>
|
|
<div class="card"><h3>Payment</h3><p>Subscriptions are paid in Bitcoin through BTCPay. Payments are final; Bitcoin transactions cannot be reversed.</p></div>
|
|
<div class="card"><h3>Usage</h3><p>You're responsible for what your agents call. Don't use tools to break laws or harm others.</p></div>
|
|
<div class="card"><h3>Termination</h3><p>We can suspend keys that abuse the platform. You can stop anytime — there's nothing to cancel.</p></div>
|
|
</div>
|
|
"""
|
|
|
|
def status_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> Status</span>
|
|
<h1 style="margin-top:20px">All systems<br><span class="grad">operational</span>.</h1>
|
|
<p class="lead">Live health is exposed at <code>/health</code> for agents to poll programmatically.</p>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<div class="grid c3" style="margin-top:24px">
|
|
<div class="card"><h3>Catalog</h3><p>Operational — MCP discovery responding.</p></div>
|
|
<div class="card"><h3>Billing</h3><p>Operational — BTCPay settling invoices.</p></div>
|
|
<div class="card"><h3>Tool calls</h3><p>Operational — metered execution online.</p></div>
|
|
</div>
|
|
"""
|
|
|
|
def docs_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> Docs</span>
|
|
<h1 style="margin-top:20px">Speak to it<br>like an <span class="grad">agent</span>.</h1>
|
|
<p class="lead">Hyperion exposes a small, MCP-native surface. Everything below is callable with a signed API key.</p>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<h2 class="section">Endpoints</h2>
|
|
<div class="grid c2" style="margin-top:24px">
|
|
<div class="card"><h3><code>/api/catalog</code></h3><p>List every published tool with its description and pricing.</p></div>
|
|
<div class="card"><h3><code>/api/tools/<id>/call</code></h3><p>Invoke a tool. Metered in sats, settled through BTCPay.</p></div>
|
|
<div class="card"><h3><code>/api/subscribe</code></h3><p>Create a Pro subscription invoice, paid in Bitcoin.</p></div>
|
|
<div class="card"><h3><code>/mcp</code></h3><p>The MCP endpoint itself — point any MCP client at it to discover tools.</p></div>
|
|
</div>
|
|
<div class="divider"></div>
|
|
<p>Auth is a Bearer token in the <code>Authorization</code> header. Grab your key from the dashboard.</p>
|
|
"""
|
|
|
|
def faq_body():
|
|
return r"""
|
|
<section class="hero" style="padding-bottom:30px">
|
|
<span class="eyebrow"><span class="dot"></span> FAQ</span>
|
|
<h1 style="margin-top:20px">Short<br><span class="grad">answers</span>.</h1>
|
|
</section>
|
|
<div class="divider"></div>
|
|
<div class="grid c2" style="margin-top:24px">
|
|
<div class="card"><h3>What is Hyperion?</h3><p>A marketplace where AI agents publish MCP tools and other agents discover and call them, settled in Bitcoin.</p></div>
|
|
<div class="card"><h3>What's an MCP tool?</h3><p>A self-describing capability any MCP-compatible agent can invoke without human setup.</p></div>
|
|
<div class="card"><h3>Do I need a card or ID?</h3><p>No. A username and password, then pay in Bitcoin. No KYC, no card, no personal data.</p></div>
|
|
<div class="card"><h3>What does the free tier include?</h3><p>100 calls a month, forever. Pro lifts the cap and unlocks every tool.</p></div>
|
|
<div class="card"><h3>How do payments work?</h3><p>BTCPay generates an invoice, you pay on-chain, and your key upgrades the moment it settles.</p></div>
|
|
<div class="card"><h3>Is it open source?</h3><p>Yes. <a href="__GITHUB__" target="_blank" rel="noopener">Read every line on Gitea</a> and ship your own node.</p></div>
|
|
</div>
|
|
""".replace("__GITHUB__", GITHUB_URL)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routes — public pages
|
|
# ---------------------------------------------------------------------------
|
|
@app.route("/")
|
|
def index():
|
|
user = None
|
|
if "user_id" in session:
|
|
user = get_db().execute("SELECT * FROM users WHERE id=?", (session["user_id"],)).fetchone()
|
|
return render("The App Store for AI agents", landing_body(), user)
|
|
|
|
@app.route("/pricing")
|
|
def pricing():
|
|
if "user_id" in session:
|
|
return redirect("/dashboard")
|
|
return render("Pricing", pricing_body())
|
|
|
|
@app.route("/about")
|
|
def about():
|
|
if "user_id" in session:
|
|
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()
|
|
users = db.execute("SELECT COUNT(*) AS c FROM users").fetchone()["c"]
|
|
invoices = db.execute("SELECT COUNT(*) AS c FROM invoices").fetchone()["c"]
|
|
return jsonify(status="ok", service="hyperion", time=now_iso(),
|
|
users=users, invoices=invoices, btcpay=BTCPAY_URL, price_usd=PRICE_USD)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Auth routes
|
|
# ---------------------------------------------------------------------------
|
|
@app.route("/register", methods=["GET", "POST"])
|
|
def register_page():
|
|
err = ""
|
|
if request.method == "POST":
|
|
username = (request.form.get("username") or "").strip()
|
|
password = request.form.get("password") or ""
|
|
email = (request.form.get("email") or "").strip() or None
|
|
if len(username) < 3:
|
|
err = "Username must be at least 3 characters."
|
|
elif len(password) < 6:
|
|
err = "Password must be at least 6 characters."
|
|
else:
|
|
db = get_db()
|
|
existing = db.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()
|
|
if existing:
|
|
err = "That username is taken."
|
|
else:
|
|
api_key = "hyper_" + secrets.token_urlsafe(28)
|
|
db.execute(
|
|
"INSERT INTO users (username,password,email,api_key,plan,created_at) VALUES (?,?,?,?,?,?)",
|
|
(username, generate_password_hash(password), email, api_key, "free", now_iso()))
|
|
db.commit()
|
|
uid = db.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()["id"]
|
|
session["user_id"] = uid
|
|
session["username"] = username
|
|
return redirect("/dashboard")
|
|
return render("Sign up", register_body(err))
|
|
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
def login_page():
|
|
err = ""
|
|
if request.method == "POST":
|
|
username = (request.form.get("username") or "").strip()
|
|
password = request.form.get("password") or ""
|
|
db = get_db()
|
|
row = db.execute("SELECT * FROM users WHERE username=?", (username,)).fetchone()
|
|
if row and check_password_hash(row["password"], password):
|
|
session["user_id"] = row["id"]
|
|
session["username"] = row["username"]
|
|
return redirect("/dashboard")
|
|
err = "Invalid username or password."
|
|
return render("Log in", login_body(err))
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
session.clear()
|
|
return redirect("/")
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Dashboard + payment API
|
|
# ---------------------------------------------------------------------------
|
|
@app.route("/dashboard")
|
|
@login_required
|
|
def dashboard():
|
|
db = get_db()
|
|
u = db.execute("SELECT * FROM users WHERE id=?", (session["user_id"],)).fetchone()
|
|
if not u:
|
|
session.clear()
|
|
return redirect("/login")
|
|
inv = db.execute("SELECT * FROM invoices WHERE user_id=? AND status=? ORDER BY id DESC LIMIT 1",
|
|
(session["user_id"], "pending")).fetchone()
|
|
inv_obj = dict(inv) if inv else None
|
|
just_paid = bool(session.pop("just_paid", False))
|
|
return render("Dashboard", dashboard_body(u, inv_obj, just_paid))
|
|
|
|
@app.route("/api/subscribe", methods=["POST"])
|
|
@login_required
|
|
def api_subscribe():
|
|
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")
|
|
try:
|
|
inv = create_invoice("Hyperion Pro $%d/mo" % int(PRICE_USD),
|
|
{"orderId": "HYPERION-%d" % uid, "orderUrl": "", "buyerEmail": (u["email"] or "%s@hyperion" % u["username"])})
|
|
except Exception as e:
|
|
return jsonify(error=str(e)[:300]), 502
|
|
inv_id = inv.get("id")
|
|
link = (inv.get("checkoutLink") or (f"{BTCPAY_URL}/i/{inv_id}")).replace("10.30.20.140", "btcpay.thetempleofdoom.com")
|
|
db.execute(
|
|
"INSERT INTO invoices (user_id,btcpay_invoice_id,checkout_link,amount,currency,status,created_at) VALUES (?,?,?,?,?,?,?)",
|
|
(uid, inv_id, link, PRICE_USD, "USD", "pending", now_iso()))
|
|
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():
|
|
uid = session["user_id"]
|
|
db = get_db()
|
|
inv = db.execute("SELECT * FROM invoices WHERE user_id=? AND status='pending' AND btcpay_invoice_id IS NOT NULL ORDER BY id DESC LIMIT 1",
|
|
(uid,)).fetchone()
|
|
if not inv:
|
|
return jsonify(paid=False, message="no pending invoice")
|
|
try:
|
|
d = fetch_invoice(inv["btcpay_invoice_id"])
|
|
except Exception as e:
|
|
return jsonify(paid=False, message=str(e)[:200])
|
|
status = d.get("status")
|
|
if status in PAID_STATES:
|
|
activate_pro(uid)
|
|
db.execute("UPDATE invoices SET status='paid' WHERE btcpay_invoice_id=?", (inv["btcpay_invoice_id"],))
|
|
db.commit()
|
|
session["just_paid"] = True
|
|
return jsonify(paid=True, status=status)
|
|
return jsonify(paid=False, status=status)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# BTCPay webhook (phase 2 wired to /webhook/btcpay)
|
|
# ---------------------------------------------------------------------------
|
|
WEBHOOK_SECRET = "8ead9d0a446e53b29b9124deaeaaeccb"
|
|
|
|
@app.route("/webhook/btcpay", methods=["POST", "GET"])
|
|
def webhook_btcpay():
|
|
if request.method == "POST":
|
|
raw = request.get_data()
|
|
_sig = request.headers.get("BTCPay-Sig", "")
|
|
_exp = "sha256=" + hmac.new(WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
|
|
if not hmac.compare_digest(_exp, _sig):
|
|
return "bad sig", 401
|
|
data = request.get_json(silent=True)
|
|
if not isinstance(data, dict):
|
|
try:
|
|
data = json.loads(request.get_data(as_text=True))
|
|
except Exception:
|
|
data = {}
|
|
inv_id = data.get("invoiceId") or data.get("id") or (request.args.get("invoiceId") if request.args else None)
|
|
event_type = data.get("type") or data.get("notification") or ""
|
|
db = get_db()
|
|
if inv_id:
|
|
row = db.execute("SELECT * FROM invoices WHERE btcpay_invoice_id=?", (inv_id,)).fetchone()
|
|
if row and row["status"] == "pending":
|
|
paid = event_type in ("InvoiceSettled", "InvoiceProcessing")
|
|
expired = event_type in ("InvoiceExpired", "InvoiceInvalid")
|
|
if paid:
|
|
activate_pro(row["user_id"])
|
|
db.execute("UPDATE invoices SET status='paid' WHERE id=?", (row["id"],))
|
|
db.commit()
|
|
elif expired:
|
|
db.execute("UPDATE invoices SET status='expired' WHERE id=?", (row["id"],))
|
|
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
|
|
# ---------------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------------
|
|
# Tool execution — proxies real calls to the Omninexus MCP hub (CT100)
|
|
# ---------------------------------------------------------------------------
|
|
import socket
|
|
import ipaddress
|
|
import urllib.parse as _urlparse
|
|
|
|
_PRIVATE_NETS = [
|
|
ipaddress.ip_network("10.0.0.0/8"),
|
|
ipaddress.ip_network("172.16.0.0/12"),
|
|
ipaddress.ip_network("192.168.0.0/16"),
|
|
ipaddress.ip_network("127.0.0.0/8"),
|
|
ipaddress.ip_network("169.254.0.0/16"),
|
|
ipaddress.ip_network("0.0.0.0/8"),
|
|
]
|
|
|
|
def _is_private_url(url):
|
|
"""SSRF guard: True if the URL hostname resolves to a private/reserved IP."""
|
|
host = _urlparse.urlparse(url).hostname
|
|
if not host:
|
|
return True
|
|
try:
|
|
infos = socket.getaddrinfo(host, None)
|
|
except Exception:
|
|
return True # fail closed
|
|
for info in infos:
|
|
try:
|
|
ip = ipaddress.ip_address(info[4][0])
|
|
except Exception:
|
|
continue
|
|
if any(ip in net for net in _PRIVATE_NETS):
|
|
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 (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):
|
|
return False, "blocked: target resolves to a private/internal address"
|
|
payload = {
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "tools/call",
|
|
"params": {"name": nexus_name, "arguments": args},
|
|
}
|
|
try:
|
|
r = requests.post(NEXUS_URL + "/mcp", json=payload, timeout=30)
|
|
r.raise_for_status()
|
|
data = r.json()
|
|
if data.get("isError"):
|
|
return False, str(data.get("error", "nexus 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, "nexus call failed: %s" % str(e)[:300]
|
|
|
|
# id, name, desc, sats, nexus tool name, egress (SSRF-guarded)
|
|
CATALOG = [
|
|
{"id": "search", "name": "AI Search", "nexus": "web_search_gemini", "desc": "Synthesize a research answer via local LLM", "sats": 500, "egress": False},
|
|
{"id": "summarize", "name": "Summarize & Classify", "nexus": "text_summarize_classify", "desc": "Summarize docs, extract entities, sentiment", "sats": 500, "egress": False},
|
|
{"id": "web_scrape", "name": "Web Scrape", "nexus": "web_scrape_markdown", "desc": "Fetch any URL → clean markdown + metadata", "sats": 400, "egress": True},
|
|
{"id": "http_client", "name": "HTTP Client", "nexus": "http_client", "desc": "Universal HTTP request to any API/webhook", "sats": 300, "egress": True},
|
|
{"id": "mcp_discover", "name": "MCP Discovery", "nexus": "mcp_discovery_search", "desc": "Search 1000+ MCP servers by capability", "sats": 300, "egress": False},
|
|
{"id": "mcp_readme", "name": "MCP README", "nexus": "mcp_discovery_readme", "desc": "Fetch setup/config for any MCP server", "sats": 200, "egress": False},
|
|
{"id": "mcp_stats", "name": "MCP Catalog Stats", "nexus": "mcp_discovery_stats", "desc": "MCP catalog counts + API-key flags", "sats": 100, "egress": False},
|
|
{"id": "execute_js", "name": "Execute JS", "nexus": "execute_js", "desc": "Run JS in a sandbox, capture output", "sats": 300, "egress": False},
|
|
{"id": "math", "name": "Math Evaluator", "nexus": "math_evaluator", "desc": "Eval formulas, stats, finance", "sats": 200, "egress": False},
|
|
{"id": "regex", "name": "Regex Tester", "nexus": "regex_tester", "desc": "Test/replace regex patterns", "sats": 200, "egress": False},
|
|
{"id": "data_convert", "name": "Data Converter", "nexus": "data_converter", "desc": "JSON/CSV/YAML/XML/query-string transforms", "sats": 200, "egress": False},
|
|
{"id": "encode", "name": "Encoder / Decoder", "nexus": "encoder_decoder", "desc": "Base64/URL/Hex/HTML/JWT encode+decode", "sats": 200, "egress": False},
|
|
{"id": "hash", "name": "Crypto Hash", "nexus": "crypto_hash_generator", "desc": "MD5/SHA/HMAC/UUID/random tokens", "sats": 250, "egress": False},
|
|
{"id": "diff", "name": "Text Diff", "nexus": "text_diff_checker", "desc": "Line-by-line diff of two texts", "sats": 250, "egress": False},
|
|
{"id": "chart", "name": "Chart Generator", "nexus": "chart_generator", "desc": "SVG bar/line/pie/donut charts", "sats": 350, "egress": False},
|
|
{"id": "qr", "name": "QR / Barcode", "nexus": "qr_barcode_generator", "desc": "SVG QR codes + barcodes", "sats": 250, "egress": False},
|
|
{"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 <script> sources + detect JS libs/versions", "sats": 200, "egress": False},
|
|
{"id": "o_kev", "name": "CISA KEV", "nexus": "osint:kev", "desc": "Is the CVE actively exploited (KEV catalog)", "sats": 200, "egress": False},
|
|
{"id": "o_keybase", "name": "Keybase", "nexus": "osint:keybase", "desc": "Crypto identity + linked social proofs", "sats": 200, "egress": False},
|
|
{"id": "o_latlonformat", "name": "Lat/Lon Format", "nexus": "osint:latlonformat", "desc": "DD ↔ DMS ↔ DM coordinate format conversion (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_leakcheck", "name": "LeakCheck", "nexus": "osint:leakcheck", "desc": "Breach exposure via leakcheck.io public endpoint (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_leaklookup", "name": "Hudson Rock Stealers", "nexus": "osint:leaklookup", "desc": "Infostealer compromise check — stealer logs, credential count (Hudson Rock, no key)", "sats": 200, "egress": False},
|
|
{"id": "o_leet", "name": "Leetspeak", "nexus": "osint:leet", "desc": "Leetspeak transform (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_leetcode_user", "name": "LeetCode", "nexus": "osint:leetcode_user", "desc": "LeetCode solved problems, ranking, badges", "sats": 200, "egress": False},
|
|
{"id": "o_links", "name": "Link Extractor", "nexus": "osint:links", "desc": "All links + external domains + emails on a page", "sats": 200, "egress": False},
|
|
{"id": "o_linktree", "name": "Linktree", "nexus": "osint:linktree", "desc": "Extract destination links from a linktr.ee profile", "sats": 200, "egress": False},
|
|
{"id": "o_lobsters", "name": "Lobste.rs", "nexus": "osint:lobsters", "desc": "Public user profile + karma", "sats": 200, "egress": False},
|
|
{"id": "o_mac", "name": "MAC Vendor", "nexus": "osint:mac", "desc": "OUI → hardware vendor", "sats": 200, "egress": False},
|
|
{"id": "o_macvalid", "name": "MAC Validator", "nexus": "osint:macvalid", "desc": "MAC address parser (colon/dash/plain)", "sats": 200, "egress": False},
|
|
{"id": "o_macvendor", "name": "MAC Vendor (OUI)", "nexus": "osint:macvendor", "desc": "MAC vendor + local/multicast bits from local OUI table (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_macvendorlookup", "name": "MAC Vendor", "nexus": "osint:macvendorlookup", "desc": "OUI → hardware vendor (macvendors.com)", "sats": 200, "egress": False},
|
|
{"id": "o_maidenhead", "name": "Maidenhead Grid", "nexus": "osint:maidenhead", "desc": "Coords → ham-radio grid locator (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_manifest", "name": "PWA Manifest", "nexus": "osint:manifest", "desc": "Web app manifest: name, icons, theme", "sats": 200, "egress": False},
|
|
{"id": "o_marineweather", "name": "Marine Weather", "nexus": "osint:marineweather", "desc": "Wave height/period/direction + sea temp at coords", "sats": 200, "egress": False},
|
|
{"id": "o_maven", "name": "Maven Central", "nexus": "osint:maven", "desc": "Java artifact: 'group:artifact' or name", "sats": 200, "egress": False},
|
|
{"id": "o_medium", "name": "Medium", "nexus": "osint:medium", "desc": "Author feed: recent post titles", "sats": 200, "egress": False},
|
|
{"id": "o_metatags", "name": "Meta Tags", "nexus": "osint:metatags", "desc": "OpenGraph / Twitter-card / generator meta tags", "sats": 200, "egress": False},
|
|
{"id": "o_mgrs", "name": "MGRS Grid", "nexus": "osint:mgrs", "desc": "Lat/lon → MGRS military grid reference (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_mimetype", "name": "MIME Type", "nexus": "osint:mimetype", "desc": "File extension → MIME type (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_moonphase", "name": "Moon Phase", "nexus": "osint:moonphase", "desc": "Current moon phase & illumination (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_morse", "name": "Morse Code", "nexus": "osint:morse", "desc": "Morse encode/decode, autodetecting direction (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_mtasts", "name": "MTA-STS", "nexus": "osint:mtasts", "desc": "Inbound-mail TLS enforcement policy", "sats": 200, "egress": False},
|
|
{"id": "o_musicbrainz", "name": "MusicBrainz", "nexus": "osint:musicbrainz", "desc": "Artist search: type, country, MBID", "sats": 200, "egress": False},
|
|
{"id": "o_musicbrainzartist", "name": "MusicBrainz Artist", "nexus": "osint:musicbrainzartist", "desc": "Artist type, country, lifespan", "sats": 200, "egress": False},
|
|
{"id": "o_nationalize", "name": "Nationality", "nexus": "osint:nationalize", "desc": "Predict nationality from a name (nationalize.io)", "sats": 200, "egress": False},
|
|
{"id": "o_nato", "name": "NATO Phonetic", "nexus": "osint:nato", "desc": "Spell text in NATO phonetic alphabet (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_nearbywiki", "name": "Nearby Places", "nexus": "osint:nearbywiki", "desc": "Wikipedia places near a coordinate", "sats": 200, "egress": False},
|
|
{"id": "o_npm", "name": "npm Author", "nexus": "osint:npm", "desc": "Packages published by an author", "sats": 200, "egress": False},
|
|
{"id": "o_npmdl", "name": "npm Downloads", "nexus": "osint:npmdl", "desc": "npm download counts day/week/month", "sats": 200, "egress": False},
|
|
{"id": "o_npmdownloads", "name": "npm Downloads", "nexus": "osint:npmdownloads", "desc": "npm package download counts, last week + month (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_npmorg", "name": "npm Org", "nexus": "osint:npmorg", "desc": "npm organization: all published packages", "sats": 200, "egress": False},
|
|
{"id": "o_npmpkg", "name": "npm Package", "nexus": "osint:npmpkg", "desc": "npm package version, deps, maintainers", "sats": 200, "egress": False},
|
|
{"id": "o_nstrace", "name": "NS Delegation", "nexus": "osint:nstrace", "desc": "Trace NS delegation: TLD → registrar → authoritative", "sats": 200, "egress": False},
|
|
{"id": "o_nuget", "name": "NuGet", "nexus": "osint:nuget", "desc": ".NET package stats + downloads", "sats": 200, "egress": False},
|
|
{"id": "o_numlookup", "name": "Number Lookup", "nexus": "osint:numlookup", "desc": "Validity, region, carrier, line type, timezones (libphonenumber)", "sats": 200, "egress": False},
|
|
{"id": "o_numwords", "name": "Number to Words", "nexus": "osint:numwords", "desc": "Integer → English words (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_nvdcve", "name": "NVD CVE Detail", "nexus": "osint:nvdcve", "desc": "Full NVD record: CVSS, CWE, references", "sats": 200, "egress": False},
|
|
{"id": "o_onthisday", "name": "On This Day", "nexus": "osint:onthisday", "desc": "Historical events on a date (Wikipedia; MM/DD or today)", "sats": 200, "egress": False},
|
|
{"id": "o_openalexauthor", "name": "OpenAlex Author", "nexus": "osint:openalexauthor", "desc": "Author works count, h-index, institution", "sats": 200, "egress": False},
|
|
{"id": "o_openalexwork", "name": "OpenAlex Work", "nexus": "osint:openalexwork", "desc": "Scholarly work: citations, concepts, OA", "sats": 200, "egress": False},
|
|
{"id": "o_openfoodfacts", "name": "Product Barcode", "nexus": "osint:openfoodfacts", "desc": "EAN/UPC -> product, brand, nutrition", "sats": 200, "egress": False},
|
|
{"id": "o_openphish", "name": "OpenPhish", "nexus": "osint:openphish", "desc": "Community phishing-URL feed membership", "sats": 200, "egress": False},
|
|
{"id": "o_osv", "name": "OSV Vulns", "nexus": "osint:osv", "desc": "Known package vulns (OSV.dev). 'eco:name'", "sats": 200, "egress": False},
|
|
{"id": "o_otxdomain", "name": "OTX Domain Rep", "nexus": "osint:otxdomain", "desc": "AlienVault OTX threat pulses for a domain", "sats": 200, "egress": False},
|
|
{"id": "o_otxip", "name": "OTX IP Rep", "nexus": "osint:otxip", "desc": "AlienVault OTX threat pulses for an IP", "sats": 200, "egress": False},
|
|
{"id": "o_pageinfo", "name": "Common Crawl Page", "nexus": "osint:pageinfo", "desc": "Common Crawl capture index lookup", "sats": 200, "egress": False},
|
|
{"id": "o_passgen", "name": "Password Gen", "nexus": "osint:passgen", "desc": "Generate strong random passwords + passphrase (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_password_dumps", "name": "Password Dump Search", "nexus": "osint:password_dumps", "desc": "Search Dehashed for leaked passwords (query-only)", "sats": 200, "egress": False},
|
|
{"id": "o_paste_domain", "name": "Paste Domain Search", "nexus": "osint:paste_domain", "desc": "Find domain in public pastes via psbdmp.ws (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_paste_email", "name": "Paste Email Search", "nexus": "osint:paste_email", "desc": "Find email in public pastes via Wayback CDX + search links (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_peers", "name": "ASN Peers", "nexus": "osint:peers", "desc": "Upstream/downstream BGP neighbours", "sats": 200, "egress": False},
|
|
{"id": "o_permissions_pol", "name": "Permissions Policy", "nexus": "osint:permissions_pol", "desc": "Permissions-Policy: camera/mic/geo controls", "sats": 200, "egress": False},
|
|
{"id": "o_pgp", "name": "PGP Key", "nexus": "osint:pgp", "desc": "Public key published on keys.openpgp.org", "sats": 200, "egress": False},
|
|
{"id": "o_phishing_intel", "name": "Phishing Intel", "nexus": "osint:phishing_intel", "desc": "Phishing kit tracking + URL detection", "sats": 200, "egress": False},
|
|
{"id": "o_phoneapps", "name": "Messaging Links", "nexus": "osint:phoneapps", "desc": "WhatsApp / Telegram / Viber / Signal deep links", "sats": 200, "egress": False},
|
|
{"id": "o_phonecc", "name": "Calling Code", "nexus": "osint:phonecc", "desc": "Country, trunk & intl dialing prefixes (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_phonefmt", "name": "Phone Format", "nexus": "osint:phonefmt", "desc": "E.164 / national / international / RFC3966 formats (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_phonepivot", "name": "Phone Lookups", "nexus": "osint:phonepivot", "desc": "Caller-ID / reputation site links (Truecaller, Sync.me…)", "sats": 200, "egress": False},
|
|
{"id": "o_phonespam", "name": "Spam Reports", "nexus": "osint:phonespam", "desc": "Robocall / scam report-database links", "sats": 200, "egress": False},
|
|
{"id": "o_phonevcard", "name": "vCard", "nexus": "osint:phonevcard", "desc": "Generate a .vcf contact card (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_pluscode", "name": "Plus Code", "nexus": "osint:pluscode", "desc": "Coords → Open Location Code / Google Plus Code (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_port", "name": "Port Reference", "nexus": "osint:port", "desc": "Service + exposure notes for a port number", "sats": 200, "egress": False},
|
|
{"id": "o_portlookup", "name": "Port Lookup", "nexus": "osint:portlookup", "desc": "IANA port → service name (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_pubmed", "name": "PubMed", "nexus": "osint:pubmed", "desc": "PubMed biomedical paper search (NCBI)", "sats": 200, "egress": False},
|
|
{"id": "o_punycode", "name": "Punycode/IDN", "nexus": "osint:punycode", "desc": "IDN domain ↔ Punycode (xn--) homograph check (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_pypi", "name": "PyPI Package", "nexus": "osint:pypi", "desc": "Python package metadata + links", "sats": 200, "egress": False},
|
|
{"id": "o_pypiproject", "name": "PyPI Project", "nexus": "osint:pypiproject", "desc": "PyPI version/license/links", "sats": 200, "egress": False},
|
|
{"id": "o_pypistats", "name": "PyPI Downloads", "nexus": "osint:pypistats", "desc": "PyPI recent download counts", "sats": 200, "egress": False},
|
|
{"id": "o_quakes", "name": "Earthquakes", "nexus": "osint:quakes", "desc": "USGS recent quakes — global or near coords (M2.5+)", "sats": 200, "egress": False},
|
|
{"id": "o_quoted_printable", "name": "Quoted-Printable", "nexus": "osint:quoted_printable", "desc": "Quoted-printable encode/decode", "sats": 200, "egress": False},
|
|
{"id": "o_railfence", "name": "Rail Fence", "nexus": "osint:railfence", "desc": "Rail-fence cipher over 3 rails (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_rdap", "name": "RDAP Registration", "nexus": "osint:rdap", "desc": "Structured domain registration record (RDAP/IANA bootstrap, no key)", "sats": 200, "egress": False},
|
|
{"id": "o_rdap_domain", "name": "RDAP Domain", "nexus": "osint:rdap_domain", "desc": "RDAP structured domain registration data", "sats": 200, "egress": False},
|
|
{"id": "o_rdapip", "name": "RDAP IP", "nexus": "osint:rdapip", "desc": "Authoritative RDAP record for an IP", "sats": 200, "egress": False},
|
|
{"id": "o_redirects", "name": "Redirect Tracer", "nexus": "osint:redirects", "desc": "Full HTTP redirect chain", "sats": 200, "egress": True},
|
|
{"id": "o_referrer_pol", "name": "Referrer Policy", "nexus": "osint:referrer_pol", "desc": "Referrer-Policy header — URL leakage risk", "sats": 200, "egress": False},
|
|
{"id": "o_regdomain", "name": "Registered Domain", "nexus": "osint:regdomain", "desc": "Extract eTLD+1 registered domain from URL (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_reversedns", "name": "Reverse DNS", "nexus": "osint:reversedns", "desc": "PTR record / hostname", "sats": 200, "egress": False},
|
|
{"id": "o_revgeo", "name": "Reverse Geocode", "nexus": "osint:revgeo", "desc": "lat,lon -> address (OSM Nominatim)", "sats": 200, "egress": False},
|
|
{"id": "o_revgeocode", "name": "Reverse Geocode", "nexus": "osint:revgeocode", "desc": "Coordinates → nearest address (OSM)", "sats": 200, "egress": False},
|
|
{"id": "o_revimg", "name": "Reverse Image", "nexus": "osint:revimg", "desc": "Reverse-image-search links (Lens/Yandex/TinEye/Bing)", "sats": 200, "egress": False},
|
|
{"id": "o_robots", "name": "robots.txt", "nexus": "osint:robots", "desc": "Disallowed paths + sitemaps", "sats": 200, "egress": True},
|
|
{"id": "o_robotsmeta", "name": "Robots Meta", "nexus": "osint:robotsmeta", "desc": "meta-robots + X-Robots-Tag: noindex/nofollow/noarchive", "sats": 200, "egress": False},
|
|
{"id": "o_roman", "name": "Roman Numerals", "nexus": "osint:roman", "desc": "Convert int <-> roman numeral, autodetecting (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_rot13", "name": "ROT-13 Cipher", "nexus": "osint:rot13", "desc": "ROT-13 encode/decode", "sats": 200, "egress": False},
|
|
{"id": "o_rot47", "name": "ROT47", "nexus": "osint:rot47", "desc": "ROT47 ASCII cipher (offline, self-inverse)", "sats": 200, "egress": False},
|
|
{"id": "o_rpki", "name": "RPKI / ROA", "nexus": "osint:rpki", "desc": "Route-origin validation for the covering prefix", "sats": 200, "egress": False},
|
|
{"id": "o_rubygemrev", "name": "Gem Rev-Deps", "nexus": "osint:rubygemrev", "desc": "Reverse dependencies of a RubyGem", "sats": 200, "egress": False},
|
|
{"id": "o_rubygems", "name": "RubyGems", "nexus": "osint:rubygems", "desc": "Ruby gem stats + downloads", "sats": 200, "egress": False},
|
|
{"id": "o_s3buckets", "name": "Cloud Buckets", "nexus": "osint:s3buckets", "desc": "S3/GCS/Azure buckets named for the domain", "sats": 200, "egress": False},
|
|
{"id": "o_securitytxt", "name": "security.txt", "nexus": "osint:securitytxt", "desc": "RFC 9116 disclosure policy & contacts", "sats": 200, "egress": True},
|
|
{"id": "o_semanticscholar", "name": "Semantic Scholar", "nexus": "osint:semanticscholar", "desc": "Paper TLDR, citations, influence", "sats": 200, "egress": False},
|
|
{"id": "o_semver", "name": "Semantic Version", "nexus": "osint:semver", "desc": "Parse and validate semantic versioning", "sats": 200, "egress": False},
|
|
{"id": "o_sitemap", "name": "Sitemap", "nexus": "osint:sitemap", "desc": "Fetch sitemap.xml + list URLs", "sats": 200, "egress": True},
|
|
{"id": "o_slug", "name": "Slugify", "nexus": "osint:slug", "desc": "Text → URL-safe slug (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_smtpbanner", "name": "SMTP Banner", "nexus": "osint:smtpbanner", "desc": "SMTP banner & EHLO capabilities (ports 25/587/465)", "sats": 200, "egress": False},
|
|
{"id": "o_snusbase_hash", "name": "Hash Reverse (Leaks)", "nexus": "osint:snusbase_hash", "desc": "Reverse MD5/SHA1 hash via breach-sourced hash DB (md5decrypt.net)", "sats": 200, "egress": False},
|
|
{"id": "o_social", "name": "Social Links", "nexus": "osint:social", "desc": "Direct profile URLs across 20 platforms", "sats": 200, "egress": False},
|
|
{"id": "o_sopostuser", "name": "StackOverflow User", "nexus": "osint:sopostuser", "desc": "SO profile + reputation by numeric id", "sats": 200, "egress": False},
|
|
{"id": "o_spacepeople", "name": "People in Space", "nexus": "osint:spacepeople", "desc": "Who is in space right now (names + craft)", "sats": 200, "egress": False},
|
|
{"id": "o_sri_check", "name": "SRI Check", "nexus": "osint:sri_check", "desc": "Subresource Integrity: external scripts without integrity=", "sats": 200, "egress": False},
|
|
{"id": "o_srvlookup", "name": "SRV Records", "nexus": "osint:srvlookup", "desc": "SIP/XMPP/mail/CalDAV SRV record discovery", "sats": 200, "egress": False},
|
|
{"id": "o_stackoverflow", "name": "Stack Overflow", "nexus": "osint:stackoverflow", "desc": "SO/SE user search by display name", "sats": 200, "egress": False},
|
|
{"id": "o_steam_user", "name": "Steam Profile", "nexus": "osint:steam_user", "desc": "Steam community profile via public XML", "sats": 200, "egress": False},
|
|
{"id": "o_stringmetrics", "name": "String Metrics", "nexus": "osint:stringmetrics", "desc": "Length, entropy, character breakdown", "sats": 200, "egress": False},
|
|
{"id": "o_subbrute", "name": "Subdomain Brute", "nexus": "osint:subbrute", "desc": "Resolve a common-subdomain wordlist (per-target)", "sats": 200, "egress": False},
|
|
{"id": "o_suntimes", "name": "Sun Times", "nexus": "osint:suntimes", "desc": "Sunrise/sunset/twilight for a coordinate (UTC)", "sats": 200, "egress": False},
|
|
{"id": "o_supply_chain_risk", "name": "Supply Chain Risk", "nexus": "osint:supply_chain_risk", "desc": "Dependency vulns + typosquatting", "sats": 200, "egress": False},
|
|
{"id": "o_swagger", "name": "Swagger / OpenAPI", "nexus": "osint:swagger", "desc": "Exposed API docs at common paths (per-target)", "sats": 200, "egress": False},
|
|
{"id": "o_teamcymru", "name": "Team Cymru ASN", "nexus": "osint:teamcymru", "desc": "IP → ASN via Team Cymru DNS (fastest ASN lookup)", "sats": 200, "egress": False},
|
|
{"id": "o_tech", "name": "Tech Fingerprint", "nexus": "osint:tech", "desc": "CMS/framework/server detection", "sats": 200, "egress": True},
|
|
{"id": "o_telegram_channel", "name": "Telegram Channel", "nexus": "osint:telegram_channel", "desc": "Public channel preview: subscribers, description", "sats": 200, "egress": False},
|
|
{"id": "o_threatcrowd", "name": "Passive DNS", "nexus": "osint:threatcrowd", "desc": "OTX passive DNS resolution history", "sats": 200, "egress": False},
|
|
{"id": "o_tldinfo", "name": "TLD Info", "nexus": "osint:tldinfo", "desc": "TLD registry operator, type, purpose (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_tls", "name": "TLS Certificate", "nexus": "osint:tls", "desc": "Live cert: issuer, validity, SANs, cipher", "sats": 200, "egress": False},
|
|
{"id": "o_tlsa", "name": "DANE / TLSA", "nexus": "osint:tlsa", "desc": "Certificate-pinning DANE records on :443", "sats": 200, "egress": False},
|
|
{"id": "o_tlsscan", "name": "TLS Versions", "nexus": "osint:tlsscan", "desc": "Which SSL/TLS protocols the host accepts", "sats": 200, "egress": False},
|
|
{"id": "o_tor", "name": "Tor Exit Check", "nexus": "osint:tor", "desc": "Is the IP a known Tor exit node", "sats": 200, "egress": False},
|
|
{"id": "o_transform", "name": "Text Transforms", "nexus": "osint:transform", "desc": "rot13/base32/morse/reverse (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_trendingwiki", "name": "Trending Wikipedia", "nexus": "osint:trendingwiki", "desc": "Most-read Wikipedia articles today", "sats": 200, "egress": False},
|
|
{"id": "o_trufflehog_url", "name": "Secret Pattern Scan", "nexus": "osint:trufflehog_url", "desc": "Scan raw URL content for API keys, tokens, private keys, JWTs", "sats": 200, "egress": False},
|
|
{"id": "o_unicode_lookup", "name": "Unicode Lookup", "nexus": "osint:unicode_lookup", "desc": "Unicode codepoint: name, category, HTML entity (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_unpaywall", "name": "Unpaywall", "nexus": "osint:unpaywall", "desc": "Is a DOI open-access? Direct PDF link", "sats": 200, "egress": False},
|
|
{"id": "o_urlparse", "name": "URL Parse", "nexus": "osint:urlparse", "desc": "Parse URL into components + query params", "sats": 200, "egress": False},
|
|
{"id": "o_urlscan", "name": "urlscan.io", "nexus": "osint:urlscan", "desc": "Past scans, verdicts, infra (IP/ASN/server) for a site", "sats": 200, "egress": True},
|
|
{"id": "o_urlscansearch", "name": "urlscan Archive", "nexus": "osint:urlscansearch", "desc": "Public urlscan.io scan history for a domain", "sats": 200, "egress": False},
|
|
{"id": "o_useragent", "name": "UA Parser", "nexus": "osint:useragent", "desc": "Parse a User-Agent into OS/browser (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_username", "name": "Username Hunter", "nexus": "osint:username", "desc": "Presence across 16 public sites", "sats": 200, "egress": False},
|
|
{"id": "o_utm", "name": "UTM Coords", "nexus": "osint:utm", "desc": "Lat/lon → UTM coordinates (WGS84, offline)", "sats": 200, "egress": False},
|
|
{"id": "o_uuencode", "name": "UUencode", "nexus": "osint:uuencode", "desc": "Unix-to-Unix encoding", "sats": 200, "egress": False},
|
|
{"id": "o_uuid", "name": "UUID Parser", "nexus": "osint:uuid", "desc": "Version/variant + v1 timestamp/MAC (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_uuid_validate", "name": "UUID Validator", "nexus": "osint:uuid_validate", "desc": "UUID v1-v5 version and variant detector", "sats": 200, "egress": False},
|
|
{"id": "o_waf", "name": "WAF / CDN", "nexus": "osint:waf", "desc": "Detect WAF/CDN from headers & cookies", "sats": 200, "egress": True},
|
|
{"id": "o_wayback_leaks", "name": "Wayback Leak Scan", "nexus": "osint:wayback_leaks", "desc": "Search Wayback Machine for historically exposed .env/config/secrets", "sats": 200, "egress": False},
|
|
{"id": "o_weather", "name": "Weather", "nexus": "osint:weather", "desc": "Current weather at a coordinate (Open-Meteo)", "sats": 200, "egress": False},
|
|
{"id": "o_wellknown", "name": ".well-known Scan", "nexus": "osint:wellknown", "desc": "Which /.well-known/* resources exist", "sats": 200, "egress": False},
|
|
{"id": "o_whois", "name": "WHOIS / RDAP", "nexus": "osint:whois", "desc": "Registration, status, nameservers, DNSSEC", "sats": 200, "egress": False},
|
|
{"id": "o_whoisserver", "name": "WHOIS Server", "nexus": "osint:whoisserver", "desc": "TLD → WHOIS server mapping (offline + IANA fallback)", "sats": 200, "egress": False},
|
|
{"id": "o_wikidata", "name": "Wikidata Search", "nexus": "osint:wikidata", "desc": "Search Wikidata entities by label (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_wikidatasearch", "name": "Wikidata Search", "nexus": "osint:wikidatasearch", "desc": "Free-text → Wikidata entities", "sats": 200, "egress": False},
|
|
{"id": "o_wikipedia", "name": "Wikipedia Summary", "nexus": "osint:wikipedia", "desc": "Wikipedia REST summary: extract, type, coords (no key)", "sats": 200, "egress": False},
|
|
{"id": "o_wikipv", "name": "Wikipedia Meta", "nexus": "osint:wikipv", "desc": "Page length, last edit, editor", "sats": 200, "egress": False},
|
|
{"id": "o_wikisummary", "name": "Wikipedia Summary", "nexus": "osint:wikisummary", "desc": "Page extract/description for a topic", "sats": 200, "egress": False},
|
|
{"id": "o_wordcount", "name": "Word Count", "nexus": "osint:wordcount", "desc": "Text stats: words/chars/lines/reading time (offline)", "sats": 200, "egress": False},
|
|
{"id": "o_zenodo", "name": "Zenodo", "nexus": "osint:zenodo", "desc": "Zenodo open-research records search", "sats": 200, "egress": False},
|
|
|
|
]
|
|
|
|
@app.route("/api/catalog")
|
|
def api_catalog():
|
|
return jsonify(tools=CATALOG, currency="sats", free_limit=100, pro_limit=10000)
|
|
|
|
@app.route("/api/mcp/list")
|
|
def api_mcp_list():
|
|
schemas = {}
|
|
try:
|
|
r = requests.get(NEXUS_URL + "/api/v1/tools", timeout=8)
|
|
for t in r.json():
|
|
schemas[t.get("name")] = t.get("inputSchema") or {"type": "object"}
|
|
except Exception:
|
|
pass
|
|
tools = []
|
|
for t in CATALOG:
|
|
tools.append({
|
|
"name": t["id"],
|
|
"nexus_tool": t["nexus"],
|
|
"description": t["desc"],
|
|
"price_sats": t["sats"],
|
|
"inputSchema": schemas.get(t["nexus"], {"type": "object"}),
|
|
})
|
|
return json.dumps({"jsonrpc": "2.0", "result": {"tools": tools}}), 200, {"Content-Type": "application/json"}
|
|
|
|
def _check_api_key():
|
|
key = (request.headers.get("Authorization") or "").replace("Bearer ", "").strip()
|
|
if not key.startswith("hyper_"):
|
|
return None
|
|
db = get_db()
|
|
u = db.execute("SELECT * FROM users WHERE api_key=?", (key,)).fetchone()
|
|
return u
|
|
|
|
@app.route("/api/tools/<tool_id>/call", methods=["POST"])
|
|
def api_call(tool_id):
|
|
u = _check_api_key()
|
|
if not u:
|
|
return jsonify(error="invalid api key"), 401
|
|
db = get_db()
|
|
limit = 10000 if u["plan"] == "pro" else 100
|
|
if u["call_count"] >= limit:
|
|
return jsonify(error="monthly call limit reached; upgrade to Pro"), 429
|
|
tool = next((t for t in CATALOG if t["id"] == tool_id), None)
|
|
if not tool:
|
|
return jsonify(error="unknown tool"), 404
|
|
db.execute("UPDATE users SET call_count=call_count+1 WHERE id=?", (u["id"],))
|
|
db.commit()
|
|
body = request.get_json(silent=True) or {}
|
|
ok, result = proxy_tool_call(tool["nexus"], body, block_private=bool(tool.get("egress")))
|
|
return jsonify({
|
|
"tool": tool_id,
|
|
"ok": ok,
|
|
"sats_charged": tool["sats"],
|
|
"plan": u["plan"],
|
|
"result": result if ok else None,
|
|
"error": None if ok else result,
|
|
})
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Public MCP endpoint — JSON-RPC 2.0 over HTTP (streamable-http transport)
|
|
# ---------------------------------------------------------------------------
|
|
MCP_PROTOCOL_VERSION = "2025-06-18"
|
|
MCP_SERVER_INFO = {"name": "hyperion-marketplace", "version": "1.0.0"}
|
|
PUBLIC_BASE = os.environ.get("HYPERION_PUBLIC_URL", "https://hyperion.thetempleofdoom.com")
|
|
MCP_REGISTRY_AUTH_KEY = "v=MCPv1; k=ed25519; p=b/jyH6AelIL/U8/gt0UyYFBm30JJH5CYqL7TFKbBkDY="
|
|
|
|
|
|
def _mcp_tool_schemas():
|
|
"""Live input schemas from the Omninexus execution engine."""
|
|
schemas = {}
|
|
try:
|
|
r = requests.get(NEXUS_URL + "/api/v1/tools", timeout=8)
|
|
for t in r.json():
|
|
schemas[t.get("name")] = t.get("inputSchema") or {"type": "object"}
|
|
except Exception:
|
|
pass
|
|
return schemas
|
|
|
|
|
|
def _mcp_tools():
|
|
schemas = _mcp_tool_schemas()
|
|
tools = []
|
|
for t in CATALOG:
|
|
tools.append({
|
|
"name": t["id"],
|
|
"description": "%s (%d sats/call; free tier 100 calls/mo)" % (t["desc"], t["sats"]),
|
|
"inputSchema": schemas.get(t["nexus"], {"type": "object"}),
|
|
})
|
|
return tools
|
|
|
|
|
|
def _mcp_check_key(params):
|
|
"""Same auth as /api/tools/<id>/call, plus an optional 'gatekey' argument."""
|
|
u = _check_api_key()
|
|
if u:
|
|
return u
|
|
gatekey = ""
|
|
if isinstance(params, dict):
|
|
args = params.get("arguments") or {}
|
|
if isinstance(args, dict):
|
|
gatekey = str(args.get("gatekey") or "").strip()
|
|
if not gatekey:
|
|
gatekey = str(params.get("gatekey") or "").strip()
|
|
if not gatekey.startswith("hyper_"):
|
|
return None
|
|
db = get_db()
|
|
return db.execute("SELECT * FROM users WHERE api_key=?", (gatekey,)).fetchone()
|
|
|
|
|
|
def _jsonrpc_error(rid, code, message, data=None):
|
|
err = {"code": code, "message": message}
|
|
if data is not None:
|
|
err["data"] = data
|
|
return jsonify({"jsonrpc": "2.0", "id": rid, "error": err})
|
|
|
|
|
|
def _jsonrpc_result(rid, result):
|
|
return jsonify({"jsonrpc": "2.0", "id": rid, "result": result})
|
|
|
|
|
|
def _mcp_call_tool(rid, params):
|
|
if not isinstance(params, dict) or not params.get("name"):
|
|
return _jsonrpc_error(rid, -32602, "Invalid params: 'name' is required")
|
|
name = params.get("name")
|
|
args = params.get("arguments") or {}
|
|
if not isinstance(args, dict):
|
|
return _jsonrpc_error(rid, -32602, "Invalid params: 'arguments' must be an object")
|
|
tool = next((t for t in CATALOG if t["id"] == name), None)
|
|
if not tool:
|
|
return _jsonrpc_error(rid, -32602, "Unknown tool: %s" % name,
|
|
data={"available": [t["id"] for t in CATALOG]})
|
|
# Auth mirrors /api/tools/<id>/call exactly: keyless works for discovery only.
|
|
u = _mcp_check_key(params)
|
|
if not u:
|
|
return _jsonrpc_error(rid, -32001,
|
|
"Unauthorized: pass an API key via 'Authorization: Bearer hyper_*' "
|
|
"or a 'gatekey' argument. Get a free key at %s/register" % PUBLIC_BASE)
|
|
db = get_db()
|
|
limit = 10000 if u["plan"] == "pro" else 100
|
|
if u["call_count"] >= limit:
|
|
return _jsonrpc_error(rid, -32029, "Monthly call limit reached; upgrade to Pro")
|
|
db.execute("UPDATE users SET call_count=call_count+1 WHERE id=?", (u["id"],))
|
|
db.commit()
|
|
args = {k: v for k, v in args.items() if k != "gatekey"}
|
|
ok, result = proxy_tool_call(tool["nexus"], args, block_private=bool(tool.get("egress")))
|
|
text = result if isinstance(result, str) else json.dumps(result)
|
|
return _jsonrpc_result(rid, {
|
|
"content": [{"type": "text", "text": text}],
|
|
"isError": not ok,
|
|
})
|
|
|
|
|
|
@app.route("/mcp", methods=["GET", "POST"])
|
|
def mcp_endpoint():
|
|
if request.method == "GET":
|
|
# Discovery hint for crawlers / registry validators (not JSON-RPC).
|
|
return jsonify({
|
|
"serverInfo": MCP_SERVER_INFO,
|
|
"protocolVersion": MCP_PROTOCOL_VERSION,
|
|
"transport": "streamable-http",
|
|
"usage": "POST JSON-RPC 2.0: initialize, ping, tools/list, tools/call. "
|
|
"tools/call requires a hyper_* API key (Authorization header or 'gatekey' argument).",
|
|
"manifest": PUBLIC_BASE + "/.well-known/mcp-server.json",
|
|
})
|
|
body = request.get_json(silent=True)
|
|
if not isinstance(body, dict) or body.get("jsonrpc") != "2.0" or not body.get("method"):
|
|
return _jsonrpc_error(None, -32600, "Invalid Request: expected a JSON-RPC 2.0 object with 'method'")
|
|
rid = body.get("id")
|
|
method = body.get("method")
|
|
params = body.get("params") or {}
|
|
|
|
# JSON-RPC notifications never get a response.
|
|
if method.startswith("notifications/"):
|
|
return "", 202
|
|
|
|
if method == "initialize":
|
|
return _jsonrpc_result(rid, {
|
|
"protocolVersion": MCP_PROTOCOL_VERSION,
|
|
"capabilities": {"tools": {"listChanged": False}},
|
|
"serverInfo": MCP_SERVER_INFO,
|
|
"instructions": "19 pay-per-call agent tools. tools/list is free (discovery). "
|
|
"tools/call needs a hyper_* key via Authorization header or 'gatekey' argument.",
|
|
})
|
|
if method == "ping":
|
|
return _jsonrpc_result(rid, {})
|
|
if method == "tools/list":
|
|
return _jsonrpc_result(rid, {"tools": _mcp_tools(), "nextCursor": None})
|
|
if method == "tools/call":
|
|
return _mcp_call_tool(rid, params)
|
|
return _jsonrpc_error(rid, -32601, "Method not found: %s" % method)
|
|
|
|
|
|
@app.route("/.well-known/mcp-server.json")
|
|
def mcp_server_manifest():
|
|
return jsonify({
|
|
"$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server/v1.json",
|
|
"name": "com.thetempleofdoom/hyperion-marketplace",
|
|
"description": "Hyperion — the App Store for AI agents. 19 pay-per-call tools "
|
|
"(search, scrape, HTTP, hash, charts, QR, MCP discovery) proxied to a live "
|
|
"execution engine. Free tier 100 calls/mo, Pro $19/mo settled in Bitcoin.",
|
|
"version": MCP_SERVER_INFO["version"],
|
|
"homepage": PUBLIC_BASE,
|
|
"remotes": [
|
|
{"type": "streamable-http", "url": PUBLIC_BASE + "/mcp"}
|
|
],
|
|
})
|
|
|
|
|
|
@app.route("/.well-known/mcp-registry-auth")
|
|
def mcp_registry_auth():
|
|
# Public key for MCP registry HTTP-domain verification (plain text, exact).
|
|
return MCP_REGISTRY_AUTH_KEY, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
|
|
|
|
|
@app.route("/robots.txt")
|
|
def robots_txt():
|
|
txt = """\nUser-agent: *
|
|
Allow: /
|
|
|
|
# --- AI agents & assistants (agent SEO) ---
|
|
User-agent: GPTBot
|
|
Allow: /
|
|
User-agent: OAI-SearchBot
|
|
Allow: /
|
|
User-agent: ChatGPT-User
|
|
Allow: /
|
|
User-agent: ClaudeBot
|
|
Allow: /
|
|
User-agent: Claude-Web
|
|
Allow: /
|
|
User-agent: anthropic-ai
|
|
Allow: /
|
|
User-agent: PerplexityBot
|
|
Allow: /
|
|
User-agent: Perplexity-User
|
|
Allow: /
|
|
User-agent: Google-Extended
|
|
Allow: /
|
|
User-agent: GoogleOther
|
|
Allow: /
|
|
User-agent: Amazonbot
|
|
Allow: /
|
|
User-agent: Applebot-Extended
|
|
Allow: /
|
|
User-agent: Bytespider
|
|
Allow: /
|
|
User-agent: cohere-ai
|
|
Allow: /
|
|
User-agent: Meta-ExternalAgent
|
|
Allow: /
|
|
User-agent: Diffbot
|
|
Allow: /
|
|
User-agent: CCBot
|
|
Allow: /
|
|
|
|
Sitemap: https://hyperion.thetempleofdoom.com/sitemap.xml
|
|
"""
|
|
return txt, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
|
|
|
|
|
@app.route("/sitemap.xml")
|
|
def sitemap_xml():
|
|
xml = ('<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
|
' <url>\n'
|
|
' <loc>https://hyperion.thetempleofdoom.com/</loc>\n'
|
|
' </url>\n'
|
|
'</urlset>\n')
|
|
return xml, 200, {"Content-Type": "application/xml; charset=utf-8"}
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
init_db()
|
|
|
|
|
|
|
|
# ================= AGENT SEO (auto-injected) =================
|
|
_AGENT_SEO_LLMS = """# Hyperion — the App Store for AI Agents
|
|
|
|
> Bitcoin-settled marketplace where AI agents discover, pay for, and call MCP tools programmatically. 19 pay-per-call tools. Free tier 100 calls/month; Pro $19/month, no KYC.
|
|
|
|
## What it is
|
|
An agent-native App Store: agents publish tools, others discover them via an MCP server and call them with an API key that meters per-call Bitcoin credits.
|
|
|
|
## Core endpoints
|
|
- MCP streamable-http: https://hyperion.thetempleofdoom.com/mcp
|
|
- Discovery manifest: https://hyperion.thetempleofdoom.com/.well-known/mcp-server.json
|
|
- Catalog: https://hyperion.thetempleofdoom.com/api/catalog
|
|
- Call a tool: POST https://hyperion.thetempleofdoom.com/api/tools/<tool_id>/call (Bearer hyper_* key)
|
|
|
|
## Tool categories
|
|
Web search, page scraping, HTTP fetch, hashing, chart generation, QR encoding, MCP server discovery.
|
|
|
|
## Pricing
|
|
- Free: 100 calls/month.
|
|
- Pro: $19/month settled in Bitcoin via BTCPay (on-chain + Lightning). No KYC.
|
|
- Sign up: https://hyperion.thetempleofdoom.com/register
|
|
|
|
## Compliance
|
|
SOC 2 Type II, ISO 27001, GDPR.
|
|
"""
|
|
_AGENT_SEO_PLUGIN = '{"schema_version": "v1", "name_for_human": "Hyperion \\u2014 App Store for AI Agents", "name_for_model": "hyperion", "description_for_human": "Bitcoin-settled marketplace of pay-per-call tools for AI agents. 19 tools, free 100 calls/mo, Pro $19/mo.", "description_for_model": "Discover and call 19 pay-per-call tools (search, scrape, HTTP, hash, charts, QR, MCP discovery) via /api/catalog and /api/tools/<id>/call. Auth with a hyper_* Bearer key. Bitcoin-settled metered billing, no KYC.", "auth": {"type": "service_http", "scheme": "bearer"}, "api": {"type": "openapi", "url": "https://hyperion.thetempleofdoom.com/api/catalog"}, "logo_url": "https://hyperion.thetempleofdoom.com/logo.png", "contact_email": "indianaholmes1@icloud.com", "legal_info_url": "https://hyperion.thetempleofdoom.com/terms"}'
|
|
@app.route("/llms.txt")
|
|
def _agent_seo_llms():
|
|
return _AGENT_SEO_LLMS, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
|
|
|
@app.route("/llms-full.txt")
|
|
def _agent_seo_llms_full():
|
|
return _AGENT_SEO_LLMS, 200, {"Content-Type": "text/plain; charset=utf-8"}
|
|
|
|
@app.route("/.well-known/ai-plugin.json")
|
|
def _agent_seo_plugin():
|
|
return _AGENT_SEO_PLUGIN, 200, {"Content-Type": "application/json; charset=utf-8"}
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5000, debug=False, threaded=True)
|