atlas-os/server/atlas_brain.py
Atlas Developer e43664a83a refactor: generalize into a distributable template — remove operator infra
- delete internal infra docs: system-of-record.md, sitemap.html status board
  (exposed live attack surface: subdomains, up/down status, client names)
- parameterize OS desktop: derive DOMAIN from location.hostname, u() helper
  builds service URLs so a friend deploying at their domain just works
- scrub all live service links to service.{DOMAIN} placeholders
- remove client domains (bicos, hkintercare, etc.) everywhere
- collapse brand/client site tiles to one generic Website tile
- single public CTA -> atlascorporation.nl (Get a workspace)
- relative SSO sign-out; sanitized server scripts to example.com

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:28:51 +02:00

938 lines
39 KiB
Python

"""Atlas Brain - the collector behind Atlas Operations.
Pulls every signal into one atlas-state.json:
mail : 7 IMAP mailboxes (unread + recent headers), strictly read-only
calendar : Google secret-iCal feeds, recurrences expanded, next 14 days
command : atlas-01 today.json (attention / mesh)
business : atlas-01 atlas_data.json (Odoo CRM)
Then synthesizes a proactive brief via the local model-router.
Supersedes jarvis-brain-hourly.py / jarvis-brain-hourly-v2.py.
Run:
python atlas_brain.py full collect + synth + write state
python atlas_brain.py --quick collect only, skip the LLM synth (fast 15-min tick)
"""
from __future__ import annotations
import base64
import email
import imaplib
import json
import os
import re
import sys
import time
import traceback
from datetime import datetime, timezone, timedelta
from email.header import decode_header, make_header
from pathlib import Path
import httpx
HOME = Path.home()
ATLAS = HOME / ".config" / "atlas"
OPS_DIR = ATLAS / "operations"
CACHE = ATLAS / "cache"
OPS_DIR.mkdir(parents=True, exist_ok=True)
CACHE.mkdir(parents=True, exist_ok=True)
STATE_JSON = OPS_DIR / "atlas-state.json"
SPOKEN_JSON = ATLAS / "atlas-brief-spoken.json"
MAIL_CFG = ATLAS / "mail-accounts.json"
CAL_CFG = ATLAS / "calendar-feeds.json"
CAL_CACHE = CACHE / "calendar-last.json"
LOG = ATLAS / "atlas-brain.log"
ATLAS01 = os.environ.get("ATLAS01_URL", "http://localhost")
A01_USER = "atlas"
A01_PASS = os.environ.get("ATLAS_BASIC_PASS", "")
SERVICES = [
{"name": "Command Center", "url": "https://atlascorporation.nl/command"},
{"name": "Nextcloud", "url": "http://atlas-01:8443"},
{"name": "MeshCentral", "url": "https://mesh.example.com/"},
{"name": "Odoo", "url": "http://<TAILSCALE_IP>:8069"},
{"name": "Atlas Portal", "url": "http://atlas-01/portal.html"},
{"name": "n8n", "url": "http://<TAILSCALE_IP>:5678"},
{"name": "Stalwart", "url": "http://<TAILSCALE_IP>:8080"},
{"name": "Authentik", "url": "http://<TAILSCALE_IP>:9000"},
{"name": "Ollama", "url": "http://localhost:11434"},
]
sys.path.insert(0, str(ATLAS))
try:
import model_router
except Exception:
model_router = None
def log(msg: str) -> None:
try:
with LOG.open("a", encoding="utf-8") as f:
f.write(f"[{datetime.now().isoformat(timespec='seconds')}] {msg}\n")
except Exception:
pass
print(f"[brain] {msg}")
def _safe(fn, name: str):
try:
return fn()
except Exception as e:
log(f"{name} FAILED: {e}")
log(traceback.format_exc())
return {"error": f"{name}: {e}"}
def _decode(s) -> str:
if not s:
return ""
try:
return str(make_header(decode_header(s)))
except Exception:
return str(s)
def _iso(d) -> str:
if isinstance(d, datetime):
if d.tzinfo is None:
d = d.replace(tzinfo=timezone.utc)
return d.astimezone().isoformat(timespec="minutes")
return str(d)
def _strip_md(text: str) -> str:
t = re.sub(r"```.*?```", "", text or "", flags=re.S)
t = re.sub(r"[`*_#>]", "", t)
t = re.sub(r"^\s*[-+]\s+", "", t, flags=re.M)
t = re.sub(r"^\s*\d+\.\s+", "", t, flags=re.M)
t = re.sub(r"\n{2,}", ". ", t)
t = re.sub(r"\n", " ", t)
t = re.sub(r"\s{2,}", " ", t)
return t.strip()
def _extract_section(md: str, name: str) -> str:
m = re.search(rf"##\s*{re.escape(name)}\s*\n(.*?)(?:\n##\s|\Z)", md or "", re.S)
return m.group(1).strip() if m else ""
# ---------------- MAIL ----------------
def collect_mail() -> dict:
if not MAIL_CFG.exists():
return {"error": "mail-accounts.json not found", "accounts": [], "total_unread": 0}
accounts = json.loads(MAIL_CFG.read_text(encoding="utf-8"))
out, total_unread = [], 0
for acc in accounts:
rec = {"label": acc["label"], "group": acc.get("group", ""),
"total": 0, "unread": 0, "recent": [], "error": None}
_last_err = None
for _attempt in range(2):
try:
M = imaplib.IMAP4_SSL(acc["host"], acc.get("port", 993), timeout=25)
M.login(acc["user"], acc["password"])
M.select("INBOX", readonly=True)
ids = M.search(None, "ALL")[1][0].split()
uids = M.search(None, "UNSEEN")[1][0].split()
rec["total"] = len(ids)
rec["unread"] = len(uids)
total_unread += len(uids)
for mid in reversed((uids or ids)[-6:]):
try:
md = M.fetch(mid.decode(), "(BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)])")[1]
raw = next((p[1] for p in md if isinstance(p, tuple)), b"")
msg = email.message_from_bytes(raw)
rec["recent"].append({
"subject": _decode(msg.get("Subject"))[:160],
"from": _decode(msg.get("From"))[:120],
"date": (msg.get("Date") or "")[:40],
"unread": mid in uids,
})
except Exception as fe:
log(f" fetch glitch {acc['label']}: {fe}")
M.logout()
_last_err = None
break # success — exit retry loop
except Exception as e:
_last_err = e
if _attempt == 0:
log(f" mail retry {acc['label']}: {e}")
time.sleep(1)
if _last_err is not None:
rec["error"] = str(_last_err)[:200]
out.append(rec)
log(f"mail {acc['label']}: {rec['unread']} unread / {rec['total']}"
+ (f" ERR {rec['error']}" if rec["error"] else ""))
return {"error": None, "accounts": out, "total_unread": total_unread,
"total_accounts": len(out)}
# ---------------- CALENDAR ----------------
def _split_events(events: list, errmsg=None) -> dict:
"""Sort events and split into today vs upcoming, by the current local date."""
events = sorted(events, key=lambda e: e.get("start", ""))
today_str = datetime.now().strftime("%Y-%m-%d")
return {
"error": errmsg,
"today": [e for e in events if e.get("start", "")[:10] == today_str],
"upcoming": [e for e in events if e.get("start", "")[:10] > today_str][:40],
"count": len(events),
}
def collect_calendar(days: int = 14) -> dict:
cfg = json.loads(CAL_CFG.read_text(encoding="utf-8")) if CAL_CFG.exists() else {}
feeds = cfg.get("feeds", [])
if not feeds:
# No live feed: re-split the cached events by today's date so a seed
# stays valid as days pass.
if CAL_CACHE.exists():
try:
cached = json.loads(CAL_CACHE.read_text(encoding="utf-8"))
res = _split_events(cached.get("events", []),
"geen live feed - getoond uit cache")
res["feeds"] = 0
return res
except Exception as e:
return {"error": f"cache unreadable: {e}", "today": [], "upcoming": [],
"feeds": 0, "count": 0}
return {"error": "no calendar feeds configured", "today": [], "upcoming": [],
"feeds": 0, "count": 0}
try:
import recurring_ical_events
from icalendar import Calendar
except Exception as e:
return {"error": f"ical libs missing ({e}) - run: pip install recurring-ical-events icalendar",
"today": [], "upcoming": [], "feeds": len(feeds), "count": 0}
now = datetime.now().astimezone()
end = now + timedelta(days=days)
events, errors = [], []
for feed in feeds:
try:
r = httpx.get(feed["url"], timeout=25, follow_redirects=True)
r.raise_for_status()
cal = Calendar.from_ical(r.content)
for ev in recurring_ical_events.of(cal).between(now, end):
start = ev.get("DTSTART").dt
dtend = ev.get("DTEND")
endt = dtend.dt if dtend else start
events.append({
"summary": str(ev.get("SUMMARY", "(no title)")),
"start": _iso(start),
"end": _iso(endt),
"location": str(ev.get("LOCATION", "")),
"calendar": feed.get("label", ""),
"kind": feed.get("kind", ""),
"all_day": not isinstance(start, datetime),
})
except Exception as e:
errors.append(f"{feed.get('label', '?')}: {str(e)[:120]}")
if events:
try:
CAL_CACHE.write_text(json.dumps(
{"events": events, "saved_at": now.isoformat(timespec="seconds")},
indent=2, ensure_ascii=False), encoding="utf-8")
except Exception:
pass
res = _split_events(events, "; ".join(errors) if errors else None)
res["feeds"] = len(feeds)
return res
# ---------------- COMMAND / BUSINESS ----------------
def _fetch_basic(url: str, timeout: int = 8):
try:
creds = base64.b64encode(f"{A01_USER}:{A01_PASS}".encode()).decode()
r = httpx.get(url, headers={"Authorization": f"Basic {creds}"},
timeout=timeout, follow_redirects=True)
r.raise_for_status()
return r.json()
except Exception as e:
return {"_error": str(e)[:160]}
def collect_command() -> dict:
today = _fetch_basic(f"{ATLAS01}/today.json")
biz = _fetch_basic(f"{ATLAS01}/atlas_data.json")
online = not (isinstance(today, dict) and today.get("_error"))
return {"today": today, "business": biz, "atlas01_online": online}
# ---------------- SERVICE HEALTH ----------------
LOCAL_SERVICES = [
{"name": "Ollama", "url": "http://localhost:11434/api/tags"},
{"name": "Operations", "url": "http://localhost:7799/"},
{"name": "Atlas CFO", "url": "http://localhost:7902/health"},
{"name": "Learning Agent", "url": "http://localhost:7901/health"},
{"name": "Atlas Portal", "url": "http://<TAILSCALE_IP>/portal.html"},
]
def collect_service_health() -> list:
results = []
for svc in LOCAL_SERVICES:
try:
r = httpx.get(svc["url"], timeout=4, follow_redirects=True)
results.append({"name": svc["name"], "url": svc["url"],
"ok": r.status_code < 400, "status": r.status_code})
except Exception as e:
results.append({"name": svc["name"], "url": svc["url"],
"ok": False, "status": 0, "error": str(e)[:80]})
return results
# ---------------- OLLAMA ----------------
def collect_ollama() -> dict:
"""Get Ollama model list and count."""
try:
r = httpx.get("http://localhost:11434/api/tags", timeout=5)
if r.status_code != 200:
return {"ok": False, "status": r.status_code}
models = r.json().get("models", [])
return {"ok": True, "model_count": len(models),
"models": [m.get("name", "") for m in models]}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- SYNCTHING ----------------
def collect_syncthing() -> dict:
"""Check Syncthing sync health via local REST API."""
import xml.etree.ElementTree as ET
import subprocess
key_path = HOME / "AppData" / "Local" / "Syncthing" / "config.xml"
try:
tree = ET.parse(key_path)
api_key = (tree.find(".//apikey") or tree.find(".//{*}apikey"))
api_key = api_key.text if api_key is not None else ""
except Exception as e:
return {"ok": False, "error": f"key_read: {e}"}
if not api_key:
return {"ok": False, "error": "apikey not found in config.xml"}
try:
r = httpx.get("http://localhost:8384/rest/system/status",
headers={"X-API-Key": api_key}, timeout=5)
d = r.json()
uptime_h = round(d.get("uptime", 0) / 3600, 1)
# Get folder completion summary
fr = httpx.get("http://localhost:8384/rest/db/completion",
headers={"X-API-Key": api_key}, timeout=5)
completion = round(fr.json().get("completion", 0), 1) if fr.status_code == 200 else None
return {"ok": True, "uptime_h": uptime_h,
"completion_pct": completion, "myID_short": d.get("myID", "")[:8]}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- TAILSCALE ----------------
def collect_tailscale() -> dict:
"""Get Tailscale mesh node status via CLI."""
import subprocess
exe = r"C:\Program Files\Tailscale\tailscale.exe"
try:
r = subprocess.run([exe, "status", "--json"],
capture_output=True, text=True, timeout=8)
if r.returncode != 0:
return {"ok": False, "error": r.stderr[:120]}
d = json.loads(r.stdout)
peers = d.get("Peer", {})
online = sum(1 for p in peers.values() if p.get("Online", False))
my_ips = d.get("TailscaleIPs") or []
my_ip = my_ips[0] if my_ips else "?"
return {"ok": True, "nodes_total": len(peers) + 1,
"nodes_online": online + 1, "peers_online": online,
"my_ip": my_ip}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- SYNTHESIS ----------------
def synthesize(state: dict) -> dict:
if model_router is None:
return {"markdown": "_model-router niet beschikbaar_", "ok": False, "model": ""}
mail = state.get("mail", {})
cal = state.get("calendar", {})
cmd = state.get("command", {})
ctx = {
"now": state["generated_at"],
"mail_unread_total": mail.get("total_unread"),
"mail_by_account": [
{"account": a["label"], "unread": a["unread"],
"recent_unread": [f"{r['from']} | {r['subject']}" for r in a["recent"] if r["unread"]][:4]}
for a in mail.get("accounts", []) if not a.get("error")
],
"today_events": [
f"{e['start'][11:16]}-{e['end'][11:16]} {e['summary']}"
+ (f" @ {e['location']}" if e["location"] else "")
for e in cal.get("today", [])
],
"upcoming_events": [
f"{e['start'][:16]} {e['summary']}" + (f" @ {e['location']}" if e["location"] else "")
for e in cal.get("upcoming", [])[:18]
],
"command_center": json.dumps(cmd.get("today", {}), default=str, ensure_ascii=False)[:1400],
"business_crm": json.dumps(cmd.get("business", {}), default=str, ensure_ascii=False)[:1400],
"odoo_summary": {
"open_tasks": state.get("odoo", {}).get("open_tasks"),
"overdue_invoices": state.get("odoo", {}).get("overdue_count"),
"outstanding_eur": state.get("odoo", {}).get("total_residual"),
},
"infrastructure": {
"atlas01_ok": state.get("atlas01", {}).get("ok"),
"atlas01_ram_pct": state.get("atlas01", {}).get("used_ram_pct"),
"atlas01_disk_free_gb": state.get("atlas01", {}).get("disk_free_gb"),
"backup_hourly_age_h": state.get("backup", {}).get("hourly_age_h"),
"backup_critical": state.get("backup", {}).get("critical"),
"pos_fleet_active": state.get("fleet", {}).get("pos_active"),
"dmarc_ok": state.get("dmarc", {}).get("ok"),
},
}
system = (
"You are Atlas Brain — the operational staff for Chaib, solo entrepreneur "
"(Atlas Agency, Atlas POS, and collaborations). You receive his mailboxes, calendar, "
"and command-center status. Write a SHARP briefing. Be concrete: name real senders, "
"real appointments, real times. Spot calendar conflicts — can he be in two places at once? "
"Flag explicitly what he risks forgetting: unread mail from important senders, appointments "
"still needing confirmation. Give 3-5 concrete actions. English only. No inventions — "
"only what is in the data."
)
user = (
"Context (JSON):\n```json\n"
+ json.dumps(ctx, indent=1, default=str, ensure_ascii=False)[:7000]
+ "\n```\n\nRespond in EXACTLY this structure (markdown):\n"
"## Headline\n<one sentence: the most important reality of today>\n\n"
"## Today's agenda\n<the appointments that actually matter; name conflicts explicitly>\n\n"
"## At risk of forgetting\n- ...\n- ...\n\n"
"## Actions today\n1. ...\n2. ...\n3. ...\n\n"
"## Mail that needs attention\n<1 to 3 lines: from whom, about what>\n\n"
"## Spoken\n<a flowing spoken paragraph of max 80 words, "
"complete sentences, no bullet points, no markdown>\n"
)
out = model_router.ask(user, task="chat", system=system, budget_s=240, num_predict=780)
return {"markdown": (out.get("text") or "").strip() or "_lege synthese_",
"ok": out.get("ok", False), "model": out.get("model", "")}
# ---------------- SPOKEN ----------------
def build_spoken(state: dict) -> dict:
mail = state.get("mail", {})
cal = state.get("calendar", {})
md = state.get("brief", {}).get("markdown", "")
now_hm = datetime.now().strftime("%H:%M")
future = [e for e in cal.get("today", []) if e.get("start", "")[11:16] >= now_hm]
nxt = future[0] if future else None
unread = mail.get("total_unread", 0)
spoken_para = _strip_md(_extract_section(md, "Spoken"))
if spoken_para and len(spoken_para) > 25:
full = spoken_para
else:
parts = [state.get("headline") or "Here is your briefing."]
if nxt:
parts.append(f"Your next appointment is {nxt['summary']} at {nxt['start'][11:16]}.")
parts.append(f"You have {unread} unread messages across your mailboxes.")
full = " ".join(parts)
sp = [f"It is {now_hm}."]
if nxt:
sp.append(f"Next appointment: {nxt['summary']} at {nxt['start'][11:16]}.")
else:
sp.append("No more appointments today.")
sp.append(f"{unread} unread emails.")
return {"full": full, "short": " ".join(sp)}
# ---------------- ODOO ----------------
def _odoo_connect():
"""Return (models_proxy, uid, DB, PASS) or raise."""
import xmlrpc.client
ODOO = os.environ.get("ATLAS_ODOO_URL", "http://<TAILSCALE_IP>:8069")
DB = os.environ.get("ATLAS_ODOO_DB", "atlas")
USER = os.environ.get("ATLAS_ODOO_USER", "chaib@atlascorporation.nl")
PASS = os.environ.get("ATLAS_ODOO_PW", "")
common = xmlrpc.client.ServerProxy(f"{ODOO}/xmlrpc/2/common")
uid = common.authenticate(DB, USER, PASS, {})
if not uid:
raise RuntimeError("Odoo auth_failed")
models = xmlrpc.client.ServerProxy(f"{ODOO}/xmlrpc/2/object")
return models, uid, DB, PASS
def collect_odoo_overdue() -> dict:
"""Fetch overdue invoices: posted, unpaid/partial, past invoice_date_due."""
try:
models, uid, DB, PASS = _odoo_connect()
today_str = datetime.now().strftime("%Y-%m-%d")
domain = [
["move_type", "in", ["out_invoice"]],
["state", "=", "posted"],
["payment_state", "in", ["not_paid", "partial"]],
["invoice_date_due", "<", today_str],
]
inv_ids = models.execute_kw(DB, uid, PASS, "account.move", "search",
[domain], {"limit": 50})
if not inv_ids:
return {"ok": True, "count": 0, "total_residual": 0.0, "invoices": []}
invoices = models.execute_kw(DB, uid, PASS, "account.move", "read", [inv_ids],
{"fields": ["name", "partner_id", "amount_residual",
"amount_total", "invoice_date_due", "payment_state"]})
result = []
total_residual = 0.0
for i in invoices:
due_str = i.get("invoice_date_due") or today_str
try:
due_dt = datetime.strptime(due_str, "%Y-%m-%d")
days_overdue = (datetime.now() - due_dt).days
except Exception:
days_overdue = 0
residual = float(i.get("amount_residual") or i.get("amount_total") or 0)
total_residual += residual
result.append({
"name": i["name"],
"partner": (i.get("partner_id") or [None, "?"])[1],
"amount_residual": round(residual, 2),
"amount_total": round(float(i.get("amount_total") or 0), 2),
"due": due_str,
"days_overdue": days_overdue,
"payment_state": i.get("payment_state", ""),
})
result.sort(key=lambda x: x["days_overdue"], reverse=True)
return {
"ok": True,
"count": len(result),
"total_residual": round(total_residual, 2),
"invoices": result,
}
except Exception as e:
return {"ok": False, "error": str(e)[:200]}
def collect_odoo() -> dict:
"""Pull task + invoice counts from self-hosted Odoo on atlas-01."""
try:
models, uid, DB, PASS = _odoo_connect()
# Open tasks
task_ids = models.execute_kw(DB, uid, PASS, "project.task", "search",
[[["stage_id.fold", "=", False]]], {"limit": 100})
tasks_count = len(task_ids)
# All unpaid invoices (posted)
today_str = datetime.now().strftime("%Y-%m-%d")
inv_ids = models.execute_kw(DB, uid, PASS, "account.move", "search",
[[["move_type", "in", ["out_invoice"]], ["state", "=", "posted"],
["payment_state", "in", ["not_paid", "partial"]]]],
{"limit": 50})
invoices = models.execute_kw(DB, uid, PASS, "account.move", "read", [inv_ids],
{"fields": ["name", "partner_id", "amount_total", "amount_residual",
"invoice_date_due", "payment_state"]})
overdue = [i for i in invoices if i.get("invoice_date_due") and
i["invoice_date_due"] < today_str]
# Detailed overdue block (reuse collect_odoo_overdue logic inline)
overdue_detail = []
total_residual = 0.0
for i in overdue:
due_str = i.get("invoice_date_due") or today_str
try:
due_dt = datetime.strptime(due_str, "%Y-%m-%d")
days_overdue = (datetime.now() - due_dt).days
except Exception:
days_overdue = 0
residual = float(i.get("amount_residual") or i.get("amount_total") or 0)
total_residual += residual
overdue_detail.append({
"name": i["name"],
"partner": (i.get("partner_id") or [None, "?"])[1],
"amount_residual": round(residual, 2),
"amount_total": round(float(i.get("amount_total") or 0), 2),
"due": due_str,
"days_overdue": days_overdue,
"payment_state": i.get("payment_state", ""),
})
overdue_detail.sort(key=lambda x: x["days_overdue"], reverse=True)
return {
"ok": True,
"open_tasks": tasks_count,
"unpaid_invoices": len(invoices),
"overdue_count": len(overdue),
"total_outstanding": round(sum(float(i.get("amount_total") or 0) for i in invoices), 2),
"total_residual": round(total_residual, 2),
"invoices": [{"name": i["name"], "partner": (i.get("partner_id") or [None, "?"])[1],
"amount": float(i.get("amount_total") or 0),
"residual": float(i.get("amount_residual") or 0),
"due": i.get("invoice_date_due"),
"state": i.get("payment_state")} for i in invoices[:8]],
"overdue_invoices": overdue_detail,
}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- DISK USAGE ----------------
def collect_disk() -> dict:
import shutil
drives = {}
for drive in ["C:\\", "D:\\"]:
try:
u = shutil.disk_usage(drive)
drives[drive[0]] = {
"total_gb": round(u.total / 1024**3, 1),
"used_gb": round(u.used / 1024**3, 1),
"free_gb": round(u.free / 1024**3, 1),
"used_pct": round(u.used / u.total * 100, 1),
"critical": (u.free / 1024**3) < 20 or (u.used / u.total) > 0.95,
"warn": (u.free / 1024**3) < 50 or (u.used / u.total) > 0.85,
}
except Exception:
pass
c = drives.get("C", {})
return {
"drives": drives,
"free_gb": c.get("free_gb"),
"used_pct": c.get("used_pct"),
"critical": c.get("critical", False),
"warn": c.get("warn", False),
}
# ---------------- SCHEDULED TASKS ----------------
def collect_scheduled_tasks() -> dict:
"""Count Windows scheduled tasks via schtasks."""
import subprocess
try:
r = subprocess.run(
["schtasks", "/query", "/fo", "CSV", "/nh"],
capture_output=True, text=True, timeout=15
)
lines = [l for l in r.stdout.splitlines() if l.strip() and not l.startswith('"TaskName"')]
atlas_lines = [l for l in lines if 'atlas' in l.lower() or 'jarvis' in l.lower()]
return {"ok": True, "total": len(lines), "atlas_count": len(atlas_lines)}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- VAULT STATS ----------------
def collect_vault_stats() -> dict:
"""Count vault notes + size from the MEGA Obsidian vault."""
import time as _time
vault = HOME / "MEGA" / "Atlas Corporation"
if not vault.exists():
return {"ok": False, "error": "vault not found"}
try:
count = 0
total_size = 0
newest_mtime = 0.0
newest_name = None
for f in vault.rglob("*.md"):
try:
st = f.stat()
count += 1
total_size += st.st_size
if st.st_mtime > newest_mtime:
newest_mtime = st.st_mtime
newest_name = f.name
except Exception:
pass
newest_age_h = round((_time.time() - newest_mtime) / 3600, 1) if newest_mtime else None
return {
"ok": True,
"note_count": count,
"size_mb": round(total_size / 1024 / 1024, 1),
"newest_note": newest_name,
"newest_age_h": newest_age_h,
}
except Exception as e:
return {"ok": False, "error": str(e)[:100]}
# ---------------- BACKUP STATUS ----------------
def collect_backup_status() -> dict:
"""Check Odoo backup freshness from the MEGA-synced backup folder."""
import time as _time
BACKUP_ROOT = HOME / "MEGA" / "Atlas Corporation" / "07_Finance" / "_atlas-odoo-backups"
result: dict = {"ok": True, "hourly_age_h": None, "daily_age_h": None, "warn": False, "critical": False}
for sub in ("hourly", "daily"):
d = BACKUP_ROOT / sub
if not d.exists():
result[f"{sub}_age_h"] = None
continue
files = sorted(d.iterdir(), key=lambda x: x.stat().st_mtime, reverse=True) if d.exists() else []
if files:
age_h = round((_time.time() - files[0].stat().st_mtime) / 3600, 1)
result[f"{sub}_age_h"] = age_h
result[f"{sub}_name"] = files[0].name
else:
result[f"{sub}_age_h"] = None
hourly = result.get("hourly_age_h")
daily = result.get("daily_age_h")
if hourly is None or hourly > 3:
result["warn"] = True
if hourly is None or hourly > 12:
result["critical"] = True
if daily is None or daily > 48:
result["warn"] = True
return result
# ---------------- ATLAS-01 RESOURCES ----------------
def collect_atlas01_resources() -> dict:
"""SSH to atlas-01 to get free RAM, CPU load, uptime, and container count."""
import subprocess
cmd = [
"ssh", "-i", str(HOME / ".ssh" / "id_ed25519_atlas"),
"-o", "BatchMode=yes", "-o", "ConnectTimeout=10",
"-o", "StrictHostKeyChecking=accept-new",
"root@<TAILSCALE_IP>",
"python3 -c \""
"import os,subprocess;"
"mem=open('/proc/meminfo').read();"
"free_kb=int([l for l in mem.splitlines() if 'MemAvailable' in l][0].split()[1]);"
"total_kb=int([l for l in mem.splitlines() if 'MemTotal' in l][0].split()[1]);"
"load=open('/proc/loadavg').read().split()[:3];"
"uptm=float(open('/proc/uptime').read().split()[0]);"
"ctrs=len([l for l in subprocess.run(['docker','ps','-q'],capture_output=True,text=True).stdout.strip().splitlines() if l]);"
"df_o=subprocess.run(['df','-BG','/'],capture_output=True,text=True).stdout.splitlines();"
"dp=df_o[1].split() if len(df_o)>1 else ['?','0G','0G','0G'];"
"d_tot=int(dp[1].rstrip('G'));d_free=int(dp[3].rstrip('G'));"
"print(f'{free_kb},{total_kb},{load[0]},{load[1]},{load[2]},{int(uptm//3600)},{ctrs},{d_tot},{d_free}')\""
]
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
if r.returncode != 0:
return {"ok": False, "error": r.stderr.strip()[:100]}
parts = r.stdout.strip().split(",")
if len(parts) < 9:
return {"ok": False, "error": "unexpected output"}
free_kb, total_kb = int(parts[0]), int(parts[1])
load1, load5, load15 = float(parts[2]), float(parts[3]), float(parts[4])
uptime_h = int(parts[5])
containers = int(parts[6])
disk_total_gb, disk_free_gb = int(parts[7]), int(parts[8])
disk_used_pct = round((disk_total_gb - disk_free_gb) / disk_total_gb * 100, 1) if disk_total_gb > 0 else 0
free_gb = round(free_kb / 1024 / 1024, 2)
used_pct = round((total_kb - free_kb) / total_kb * 100, 1)
return {
"ok": True,
"free_ram_gb": free_gb,
"total_ram_gb": round(total_kb / 1024 / 1024, 2),
"used_ram_pct": used_pct,
"load_1m": load1,
"load_5m": load5,
"load_15m": load15,
"uptime_h": uptime_h,
"containers": containers,
"disk_total_gb": disk_total_gb,
"disk_free_gb": disk_free_gb,
"disk_used_pct": disk_used_pct,
"disk_critical": disk_free_gb < 10,
"warn": used_pct > 85 or load1 > 4.0 or disk_free_gb < 20,
}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- AUCTION INTEL ----------------
def collect_auction_intel() -> dict:
"""Pull live stats from the auction intel dashboard API (localhost:7781)."""
import sqlite3
intel_db = HOME / "twa-scrape" / "intel.db"
if not intel_db.exists():
return {"ok": False, "reason": "db_not_found"}
try:
conn = sqlite3.connect(str(intel_db))
conn.row_factory = sqlite3.Row
active = conn.execute("SELECT COUNT(*) FROM lots WHERE status='active'").fetchone()[0]
strong = conn.execute("SELECT COUNT(*) FROM lots WHERE status='active' AND stars>=4").fetchone()[0]
avg_m = conn.execute("SELECT AVG(margin_pct) FROM lots WHERE status='active' AND margin_pct IS NOT NULL").fetchone()[0]
try:
urgent = conn.execute("SELECT COUNT(*) FROM lots WHERE status='active' AND urgency=1").fetchone()[0]
except Exception:
urgent = 0
top5 = [dict(r) for r in conn.execute(
"SELECT id, title, model_key, hammer, margin_pct, stars, close_time "
"FROM lots WHERE status='active' AND stars>=4 "
"ORDER BY stars DESC, margin_pct DESC LIMIT 5"
).fetchall()]
try:
last_run = dict(conn.execute(
"SELECT started_at, run_type, lots_found FROM run_log ORDER BY id DESC LIMIT 1"
).fetchone() or {})
except Exception:
last_run = {}
conn.close()
return {
"ok": True,
"lots_active": active,
"strong_buys": strong,
"urgent_count": urgent,
"avg_margin": round(avg_m or 0, 1),
"top_lots": top5,
"last_run": last_run,
"dashboard_url": "http://localhost:7781/",
}
except Exception as e:
return {"ok": False, "reason": str(e)}
# ---------------- POS FLEET ----------------
def collect_pos_fleet() -> dict:
"""Read POS fleet config — static count from pos_targets.json."""
pts = ATLAS / "pos_targets.json"
if not pts.exists():
return {"ok": False, "reason": "pos_targets.json not found"}
try:
cfg = json.loads(pts.read_text(encoding="utf-8"))
pos = cfg.get("pos_agents", [])
internal = cfg.get("atlas_internal", [])
active = [p for p in pos if not p.get("skip_autopilot")]
skipped = [p for p in pos if p.get("skip_autopilot")]
return {
"ok": True,
"pos_count": len(pos),
"pos_active": len(active),
"pos_skipped": len(skipped),
"internal_count": len(internal),
"skip_reasons": [{"name": p["dws_name"], "reason": p.get("skip_reason", "?")[:60]}
for p in skipped],
}
except Exception as e:
return {"ok": False, "error": str(e)[:120]}
# ---------------- DMARC CHECK ----------------
def collect_dmarc() -> dict:
"""DNS check for DMARC policy on Atlas domains. p=none = not enforced."""
import socket
domains = ["atlascorporation.nl", "atlasworks.nl", "atlasworkspace.eu", "atlasoperations.nl"]
results = {}
for d in domains:
try:
txt = socket.getaddrinfo(f"_dmarc.{d}", None)
except Exception:
txt = None
try:
import subprocess
r = subprocess.run(
["nslookup", "-type=TXT", f"_dmarc.{d}", "8.8.8.8"],
capture_output=True, text=True, timeout=8
)
raw = r.stdout
if "v=DMARC1" in raw:
policy = "quarantine" if "p=quarantine" in raw else (
"reject" if "p=reject" in raw else "none")
results[d] = {"ok": policy != "none", "policy": policy}
else:
results[d] = {"ok": False, "policy": "missing"}
except Exception as e:
results[d] = {"ok": None, "error": str(e)[:80]}
enforced = [d for d, v in results.items() if v.get("ok")]
missing = [d for d, v in results.items() if not v.get("ok")]
return {"ok": len(missing) == 0, "domains": results,
"enforced": enforced, "missing": missing}
# ---------------- MAIN ----------------
def main() -> None:
quick = "--quick" in sys.argv
send_report = "--report" in sys.argv
t0 = time.time()
state = {
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"generated_unix": int(time.time()),
"operator": "Chaib",
"mode": "quick" if quick else "full",
}
state["mail"] = _safe(collect_mail, "mail")
state["calendar"] = _safe(collect_calendar, "calendar")
state["command"] = _safe(collect_command, "command")
state["service_health"] = _safe(collect_service_health, "service_health")
state["syncthing"] = _safe(collect_syncthing, "syncthing")
state["tailscale"] = _safe(collect_tailscale, "tailscale")
state["ollama"] = _safe(collect_ollama, "ollama")
state["scheduled_tasks"] = _safe(collect_scheduled_tasks, "scheduled_tasks")
state["auction_intel"] = _safe(collect_auction_intel, "auction_intel")
state["disk"] = _safe(collect_disk, "disk")
state["atlas01"] = _safe(collect_atlas01_resources, "atlas01")
state["backup"] = _safe(collect_backup_status, "backup")
state["fleet"] = _safe(collect_pos_fleet, "fleet")
state["dmarc"] = _safe(collect_dmarc, "dmarc")
# Slow collectors — only on full runs; quick ticks carry forward the last state
if not quick:
state["vault"] = _safe(collect_vault_stats, "vault") # 5s rglob over 10k files
state["odoo"] = _safe(collect_odoo, "odoo")
# Store overdue invoices as its own top-level key for check_overdue.py / portal
odoo_data = state["odoo"]
if odoo_data.get("ok"):
state["overdue_invoices"] = {
"ok": True,
"count": odoo_data.get("overdue_count", 0),
"total_residual": odoo_data.get("total_residual", 0.0),
"invoices": odoo_data.get("overdue_invoices", []),
"updated_at": state["generated_at"],
}
else:
state["overdue_invoices"] = {"ok": False, "error": odoo_data.get("error", "odoo_error"),
"updated_at": state["generated_at"]}
else:
try:
prev = json.loads(STATE_JSON.read_text(encoding="utf-8")) if STATE_JSON.exists() else {}
state["odoo"] = prev.get("odoo", {"ok": False, "error": "quick_tick_no_refresh"})
state["overdue_invoices"] = prev.get("overdue_invoices",
{"ok": False, "error": "quick_tick_no_refresh"})
state["vault"] = prev.get("vault", {"ok": False, "error": "quick_tick_no_refresh"})
except Exception:
state["odoo"] = {"ok": False, "error": "quick_tick_no_refresh"}
state["overdue_invoices"] = {"ok": False, "error": "quick_tick_no_refresh"}
state["vault"] = {"ok": False, "error": "quick_tick_no_refresh"}
if quick:
state["brief"] = {"markdown": f"_quick tick {state['generated_at'][11:16]} — no synthesis_",
"ok": False, "model": ""}
else:
state["brief"] = _safe(lambda: synthesize(state), "synth")
state["headline"] = (_extract_section(state["brief"].get("markdown", ""), "Headline").splitlines()
or _extract_section(state["brief"].get("markdown", ""), "Kop").splitlines()
or ["Atlas briefing."])[0].strip().lstrip("#").strip() or "Atlas briefing."
state["spoken"] = build_spoken(state)
state["services"] = SERVICES # static link list for portal
state["build_seconds"] = round(time.time() - t0, 1)
state_bytes = json.dumps(state, indent=2, default=str, ensure_ascii=False)
STATE_JSON.write_text(state_bytes, encoding="utf-8")
# Mirror to state/ sub-dir (served by the 7799 HTTP server)
(OPS_DIR / "state" / "atlas-state.json").write_text(state_bytes, encoding="utf-8")
SPOKEN_JSON.write_text(json.dumps(state["spoken"], indent=2, ensure_ascii=False), encoding="utf-8")
try:
cmd_dir = HOME / "MEGA" / "Atlas Corporation" / "vault" / "_command"
if cmd_dir.exists():
(cmd_dir / "jarvis-brief.md").write_text(state["brief"].get("markdown", ""), encoding="utf-8")
except Exception:
pass
log(f"state written -> {STATE_JSON} ({state['build_seconds']}s, "
f"mail_unread={state['mail'].get('total_unread')}, "
f"today_events={len(state['calendar'].get('today', []))})")
if send_report and not quick:
try:
import subprocess
py = sys.executable
rpt = Path(__file__).parent / "atlas_morning_report.py"
subprocess.Popen([py, str(rpt)], creationflags=0x08000000)
log("Morning report spawned")
except Exception as e:
log(f"Morning report failed: {e}")
print(json.dumps({
"ok": True, "mode": state["mode"], "state_file": str(STATE_JSON),
"mail_unread": state["mail"].get("total_unread"),
"today_events": len(state["calendar"].get("today", [])),
"headline": state["headline"], "seconds": state["build_seconds"],
}, indent=2, ensure_ascii=False))
if __name__ == "__main__":
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
main()