#!/usr/bin/env python3
"""intrikata — CLI for the praxis stack.

Prefers a live local Pharosiraptor graph server (http://localhost:8080).
Falls back to the published, scrubbed snapshot at intrikata.pages.dev /
intrikata.com. Always prints which source answered — numbers are never mixed
between sources in one run.

Single file, stdlib only, Python 3.9+.

  py intrikata.py provision <handle> [-o file]   # download isolated minimum graphs (local import)
  py intrikata.py bootstrap <handle>             # complete CF D1 minimal Pharosiraptor operant
  py intrikata.py operant <handle> [--q Q]       # probe live CF operant health + node search
  py intrikata.py onboard-codex                  # Codex install-once short-circuit path
  py intrikata.py onboard-grok                   # Grok Build short-circuit path (no MCP/swarm)
  py intrikata.py status
  py intrikata.py graphs [--all]
  py intrikata.py coverage
  py intrikata.py traps [-v]
  py intrikata.py bridges
  py intrikata.py timeline
"""
import argparse
import hashlib
import json
import os
import re
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path

LOCAL = "http://localhost:8080"
# Canonical public host (swarm H3). Override with INTRIKATA_BASE env if needed.
API_BASE = (os.environ.get("INTRIKATA_BASE") or "https://intrikata.com").rstrip("/")
PUBLISHED = API_BASE + "/data/snapshot.json"
PROVISION_URL = API_BASE + "/data/provision.json"
CODEX_ONBOARD_URL = API_BASE + "/data/codex-onboard.json"
GROK_ONBOARD_URL = API_BASE + "/data/grok-onboard.json"
HEADERS = {"User-Agent": "intrikata-cli/1.0"}  # CF edge 403s default urllib UA


def fetch(url, timeout=8):
    req = urllib.request.Request(url, headers=HEADERS)
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return json.loads(r.read().decode("utf-8"))


def local_alive():
    try:
        return fetch(LOCAL + "/api/health", timeout=2).get("status") == "healthy"
    except Exception:
        return False


def load_snapshot():
    snap = fetch(PUBLISHED)
    print(f"source: published snapshot ({snap.get('generated_at', '?')})")
    return snap


def bar(v, mx, w=22):
    if not mx:
        return "." * w
    f = round(w * v / mx)
    return "#" * f + "." * (w - f)


def cmd_status(args):
    if local_alive():
        h = fetch(LOCAL + "/api/health")
        print("source: live local graph server")
        print(f"status       {h.get('status')}")
        print(f"version      {h.get('version')}")
        graphs = fetch(LOCAL + "/api/graphs")
        print(f"graphs       {len(graphs)}")
        return
    snap = load_snapshot()
    g, c, t, b = snap.get("graphs", {}), snap.get("coverage", {}), snap.get("traps"), snap.get("bridges", {})
    print(f"graphs       {g.get('total')}  ({g.get('nodes_total')} nodes)")
    print(f"completeness {c.get('completeness')}")
    if t:
        print(f"traps        {t['closed']} closed / {t['open']} open / {t['doc_only']} doc-only / {t['na']} n-a / {t['meta']} meta")
    if b:
        drift = "no drift" if b.get("drift") == 0 else f"DRIFT {b.get('drift')}"
        print(f"bridges      {b.get('resolved_active')}/{b.get('total')} resolved · {drift}")


def cmd_graphs(args):
    if local_alive():
        print("source: live local graph server")
        rows = [{"name": g.get("name", ""), "updated": (g.get("updated_at") or "")[:10]}
                for g in fetch(LOCAL + "/api/graphs")]
        rows.sort(key=lambda r: r["updated"], reverse=True)
        nodes_known = False
    else:
        snap = load_snapshot()
        rows = snap.get("graphs", {}).get("list", [])
        nodes_known = True
    n = len(rows) if args.all else 20
    for r in rows[:n]:
        nodes = f"{r.get('nodes'):>7}" if nodes_known and r.get("nodes") is not None else "      —"
        print(f"  {r['name'][:52]:<53}{nodes}  {r.get('updated', '—')}")
    if n < len(rows):
        print(f"  + {len(rows) - n} more (use --all)")


def cmd_coverage(args):
    if local_alive():
        cwd = urllib.parse.quote(str(Path.home() / "Documents"), safe="")
        mgs = fetch(LOCAL + f"/api/megapraxis/mgs_coverage?cwd={cwd}")
        ag = fetch(LOCAL + f"/api/megapraxis/agents_coverage?cwd={cwd}")
        comp = fetch(LOCAL + f"/api/megapraxis/verify_completeness?cwd={cwd}")
        print("source: live local graph server")
        data = {
            "skills": {"enabled": mgs["totals"]["total"] - mgs["totals"]["dark"], "dark": mgs["totals"]["dark"],
                       "per_layer": mgs["totals"]["per_layer"], "tarski_unclassified": mgs["totals"]["unclassified"]},
            "agents": {"enabled": ag["totals"]["total"] - ag["totals"]["dark"], "dark": ag["totals"]["dark"],
                       "per_layer": ag["totals"]["per_layer"], "tarski_unclassified": ag["totals"]["unclassified"]},
            "completeness": comp["converge"]["verdict"],
        }
    else:
        data = load_snapshot().get("coverage", {})
    for label in ("skills", "agents"):
        part = data.get(label)
        if not part:
            continue
        print(f"{label}: {part['enabled']} enabled · {part['dark']} dark · tarski-unclassified {part['tarski_unclassified']}")
        layers = part.get("per_layer", {})
        mx = max(layers.values(), default=1)
        for k, v in layers.items():
            print(f"  {k:<9}{bar(v, mx)} {v:>4}")
    print(f"verdict: {data.get('completeness')}")


def cmd_traps(args):
    t = load_snapshot().get("traps")  # roll-call lives in the audit baseline; snapshot is its published form
    if not t:
        print("no audit baseline in snapshot")
        return
    print(f"{t['closed']} closed / {t['open']} open / {t['doc_only']} doc-only / {t['na']} n-a / {t['meta']} meta")
    if args.verbose:
        for r in t["roll_call"]:
            print(f"  {r['n']:>2} {r['name']:<42}{r['status'] or '—'}")
    for note in t.get("notes", []):
        print(f"  note: {note}")


def cmd_bridges(args):
    b = load_snapshot().get("bridges", {})
    drift = "no drift" if b.get("drift") == 0 else f"DRIFT {b.get('drift')}"
    print(f"{b.get('resolved_active')}/{b.get('total')} resolved · {drift}")
    for e in b.get("list", []):
        print(f"  {e['name'].replace('bridge::', ''):<44}{e['class']:<14}{e['compiled']}")


def cmd_timeline(args):
    for e in load_snapshot().get("timeline", []):
        print(f"  {e['date']}  {e['kind']:<7}{e['verdict']:<32}ref {e['ref']}")


def cmd_provision(args):
    """Derive this handle's isolated copy of the minimum megapraxis/metamegapraxis graphs."""
    prov = fetch(PROVISION_URL)
    handle = args.handle.strip()
    iid = hashlib.sha256(("intrikata " + handle).encode()).hexdigest()[:12]
    graphs = []
    for g in prov.get("graphs", []):
        if not g.get("present"):
            continue
        gid = hashlib.sha256((iid + ":" + g["template_id"]).encode()).hexdigest()[:16]
        graphs.append({
            "instance_graph_id": gid, "label": g["label"], "role": g["role"],
            "nodes_total": g["nodes_total"], "provisioned": g["provisioned"],
            "audit_stripped": g["audit_stripped"], "truncated": g["truncated"],
            "seed_nodes": g.get("seed_nodes", []),
        })
    seed_total = sum(len(g["seed_nodes"]) for g in graphs)
    bundle = {
        "instance": iid, "handle": handle, "kind": prov.get("kind"),
        "template_generated_at": prov.get("generated_at"),
        "note": prov.get("note", "") + f" This copy is namespaced to instance {iid}; graph ids are unique to you.",
        "graphs": graphs, "totals": {"graphs": len(graphs), "seed_nodes": seed_total},
    }
    out = args.out or f"intrikata-{iid}.json"
    Path(out).write_text(json.dumps(bundle, indent=1), encoding="utf-8")
    print(f"source: published minimal template ({prov.get('generated_at', '?')})")
    print(f"instance {iid} for '{handle}' — only the minimum graphs, namespaced to you:")
    for g in graphs:
        cnt = f"{g['provisioned']}" + (f"/{g['nodes_total']}" if g["truncated"] else "")
        print(f"  {g['label']:34}{cnt:>10} seed   graph-id {g['instance_graph_id']}")
    print(f"wrote {out}  ({len(graphs)} graphs, {seed_total} seed nodes)")
    print("local import path — or:  intrikata bootstrap <handle>  for Cloudflare D1 operant")


def api_post(path, body, timeout=60):
    req = urllib.request.Request(
        API_BASE + path,
        data=json.dumps(body).encode("utf-8"),
        headers={**HEADERS, "Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read().decode("utf-8"))
        except Exception:
            return {"error": f"HTTP {e.code}"}


def api_get(path, timeout=20):
    req = urllib.request.Request(API_BASE + path, headers=HEADERS)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as r:
            return json.loads(r.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        try:
            return json.loads(e.read().decode("utf-8"))
        except Exception:
            return {"error": f"HTTP {e.code}"}


def cmd_bootstrap(args):
    """Complete per-instance minimal Pharosiraptor operant on Cloudflare D1."""
    handle = args.handle.strip()
    print(f"bootstrapping CF operant for '{handle}'…")
    r = api_post("/api/bootstrap", {"handle": handle}, timeout=90)
    if r.get("error"):
        sys.exit(f"intrikata: {r['error']}")
    state = "already operant" if r.get("already_provisioned") else "operant ready"
    print(f"{state}: {r.get('instance_id')}  graphs={r.get('graphs')} seed_nodes={r.get('seed_nodes')}")
    print(f"provisioned_at  {r.get('provisioned_at')}")
    op = r.get("operant") or {}
    if op:
        print(f"health  {API_BASE}{op.get('health')}")
        print(f"graphs  {API_BASE}{op.get('graphs')}")
        print(f"nodes   {API_BASE}{op.get('nodes')}?q=Swarm::")
    if r.get("swarm_invocation"):
        print(r["swarm_invocation"])
    print("probe with:  intrikata operant", handle)


def cmd_operant(args):
    """Probe live CF operant health and optional node search."""
    handle = args.handle.strip()
    iid = hashlib.sha256(("intrikata " + handle).encode()).hexdigest()[:12]
    q = (args.q or "Swarm::").strip()
    h = api_get(f"/api/operant/{iid}/health")
    if h.get("error") or h.get("status") == "not_provisioned":
        sys.exit(f"intrikata: {h.get('error') or h.get('status')} — run: intrikata bootstrap {handle}")
    print(f"source: CF operant D1")
    print(f"status       {h.get('status')}  ({h.get('operant')})")
    print(f"instance     {h.get('instance_id')}  handle={h.get('handle')}")
    print(f"graphs       {h.get('graphs')}  nodes_live={h.get('nodes_live')}  seed={h.get('seed_nodes')}")
    print(f"provisioned  {h.get('provisioned_at')}")
    for g in h.get("graph_list") or []:
        print(f"  {g.get('label', ''):34}{g.get('provisioned', 0):>6}  {g.get('graph_id')}")
    n = api_get(f"/api/operant/{iid}/nodes?q={urllib.parse.quote(q)}&limit=12")
    if n.get("error"):
        print(f"nodes error: {n['error']}")
        return
    print(f"nodes q={q!r}  hits={n.get('count')}")
    for node in n.get("nodes") or []:
        print(f"  {(node.get('kind') or 'node'):10} {(node.get('name') or '')[:72]}")


def _enroll_path():
    return Path.home() / ".intrikata-enroll.json"


def _load_enroll(handle):
    p = _enroll_path()
    if not p.is_file():
        return None
    try:
        data = json.loads(p.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(data, dict) or data.get("handle") != handle:
        return None
    exp = data.get("exp") or 0
    if exp and time.time() > exp:
        try:
            p.unlink()
        except OSError:
            pass
        return None
    return data.get("token")


def _save_enroll(handle, token, ttl_sec=1800):
    if not handle or not token:
        return
    payload = {
        "handle": handle,
        "token": token,
        "exp": time.time() + max(60, int(ttl_sec or 1800) - 30),
    }
    _enroll_path().write_text(json.dumps(payload), encoding="utf-8")


def cmd_link(args):
    body = {"handle": args.handle, "contact": args.contact}
    token = getattr(args, "enroll_token", None) or _load_enroll(args.handle)
    if token:
        body["enroll_token"] = token
    r = api_post("/api/link/start", body)
    if r.get("error"):
        if r.get("payment_url"):
            print(f"add a payment method (Link/Stripe): {r['payment_url']}")
        if r.get("error") == "mfa_proof_required":
            chans = "+".join(r.get("claimed_channels") or []) or "?"
            print(f"handle already has factors: {chans}")
            print("re-verify an existing factor first, then link the new contact within 30m:")
            print(f"  intrikata link {args.handle} <existing-contact>")
            print(f"  intrikata verify {args.handle} <existing-contact> <code>")
            print(f"  intrikata link {args.handle} {args.contact}")
        sys.exit(f"intrikata: {r['error']}")
    note = " (MFA add)" if r.get("mfa_add") else ""
    print(f"code sent via {r['channel']}{note} — complete with:")
    print(f"  intrikata verify {args.handle} {args.contact} <code>")


def _write_recovery_file(r, default_name=None):
    body = r.get("recovery_file_body")
    key = r.get("recovery_key")
    if not body and not key:
        return None
    name = r.get("recovery_file_name") or default_name or f"intrikata-recovery-{r.get('handle', 'handle')}.txt"
    if not body:
        body = (
            "intrikata recovery key\n"
            f"handle: {r.get('handle', '')}\n"
            f"instance_id: {r.get('instance_id', '')}\n"
            f"recovery_key:\n{key}\n"
        )
    path = Path.cwd() / name
    path.write_text(body, encoding="utf-8")
    return path


def cmd_verify(args):
    body = {"handle": args.handle, "contact": args.contact, "code": args.code}
    token = getattr(args, "enroll_token", None) or _load_enroll(args.handle)
    if token:
        body["enroll_token"] = token
    if getattr(args, "rkey", False):
        body["regenerate_recovery_key"] = True
    r = api_post("/api/link/verify", body)
    if r.get("error"):
        if r.get("payment_url"):
            print(f"add a payment method (Link/Stripe): {r['payment_url']}")
        if r.get("hint"):
            print(r["hint"])
        sys.exit(f"intrikata: {r['error']}")
    if r.get("enroll_token"):
        _save_enroll(r["handle"], r["enroll_token"], r.get("enroll_ttl_sec") or 1800)
    chans = "+".join(r.get("channels") or []) or r.get("channel")
    mfa = "MFA complete" if r.get("mfa_complete") else "MFA partial"
    print(f"linked {r['handle']} ({r['instance_id']}) via {r['channel']} · {chans} · {mfa}")
    if r.get("recovery_key") or r.get("recovery_file_body"):
        path = _write_recovery_file(r)
        if path:
            print(f"recovery key written: {path}")
            print("store offline — previous key revoked if this was a regenerate")
    if r.get("next"):
        print(r["next"])


def cmd_recover_key(args):
    key = (args.key or "").strip()
    if args.file:
        text = Path(args.file).read_text(encoding="utf-8", errors="replace")
        m = re.search(r"ikrec_v1_[0-9a-f-]+", text, re.I)
        if not m:
            sys.exit("intrikata: no ikrec_v1_… key found in file")
        key = m.group(0)
    if not key:
        sys.exit("intrikata: provide --key or --file")
    r = api_post("/api/recover/key", {"recovery_key": key, "regenerate": not args.no_rotate})
    if r.get("error"):
        sys.exit(f"intrikata: {r['error']}")
    print(f"recovered {r['handle']} ({r['instance_id']}) · {'+'.join(r.get('channels') or [])}")
    if r.get("recovery_key") or r.get("recovery_file_body"):
        path = _write_recovery_file(r)
        if path:
            print(f"new recovery key written: {path}")
    if r.get("note"):
        print(r["note"])


def cmd_link_mtls(args):
    """Issue 4th-factor mTLS device cert (requires live tunnel session)."""
    body = {
        "handle": args.handle,
        "session_id": args.session,
        "rotate": bool(args.rotate),
    }
    if args.enroll_token:
        body["enroll_token"] = args.enroll_token
    else:
        tok = _load_enroll(args.handle)
        if tok:
            body["enroll_token"] = tok
    r = api_post("/api/link/mtls/issue", body)
    if r.get("error"):
        if r.get("hint"):
            print(r["hint"])
        sys.exit(f"intrikata: {r['error']}")
    name = r.get("bundle_file_name") or f"intrikata-mtls-{args.handle}.pem"
    body_txt = r.get("bundle_file_body") or ((r.get("cert_pem") or "") + "\n" + (r.get("key_pem") or ""))
    path = Path.cwd() / name
    path.write_text(body_txt, encoding="utf-8")
    print(f"mTLS linked {r['handle']} · factors={r.get('factors')} · tunnel={r.get('mtls_tunnel_host')}")
    print(f"device cert written: {path}")
    print("store offline — private key is not on the server")
    if r.get("enroll_token"):
        _save_enroll(r["handle"], r["enroll_token"], r.get("enroll_ttl_sec") or 1800)
    if r.get("note"):
        print(r["note"])


def cmd_recover_mtls(args):
    """Prove mTLS device key (challenge-response)."""
    try:
        from cryptography.hazmat.primitives import hashes, serialization
        from cryptography.hazmat.primitives.asymmetric import ec
        from cryptography.hazmat.backends import default_backend
        import base64
    except ImportError:
        sys.exit(
            "intrikata: recover-mtls CLI requires the cryptography package "
            "(pip install cryptography) — or use the console recover-mtls command"
        )
    pem_text = Path(args.file).read_text(encoding="utf-8", errors="replace")
    # Extract PRIVATE KEY block
    m = re.search(
        r"-----BEGIN (?:EC )?PRIVATE KEY-----[\s\S]+?-----END (?:EC )?PRIVATE KEY-----",
        pem_text,
    )
    if not m:
        sys.exit("intrikata: no PRIVATE KEY block in file")
    key = serialization.load_pem_private_key(
        m.group(0).encode("utf-8"), password=None, backend=default_backend()
    )
    chal = api_post("/api/recover/mtls/challenge", {"handle": args.handle})
    if chal.get("error"):
        sys.exit(f"intrikata: {chal['error']}")
    sig = key.sign(
        chal["challenge"].encode("utf-8"),
        ec.ECDSA(hashes.SHA256()),
    )
    # cryptography returns DER; server accepts DER or raw
    signature_b64 = base64.b64encode(sig).decode("ascii")
    r = api_post(
        "/api/recover/mtls/verify",
        {
            "handle": args.handle,
            "challenge": chal["challenge"],
            "signature_b64": signature_b64,
        },
    )
    if r.get("error"):
        sys.exit(f"intrikata: {r['error']}")
    print(f"recovered via mTLS {r['handle']} ({r['instance_id']}) · {'+'.join(r.get('channels') or [])}")
    if r.get("enroll_token"):
        _save_enroll(r["handle"], r["enroll_token"], r.get("enroll_ttl_sec") or 1800)
    if r.get("note"):
        print(r["note"])


def cmd_onboard_grok(args):
    """Print the Grok short-circuit path; prefer live cert when reachable."""
    cert = None
    try:
        cert = fetch(GROK_ONBOARD_URL, timeout=6)
        print(f"source: published cert ({cert.get('generated_at', '?')})")
    except Exception as exc:
        print(f"source: local fallback (cert fetch failed: {exc})")
    print("Grok Build — intrikata onboard short-circuit")
    print("verdict     converged-modulo-harness")
    print("class       onboarding-short-circuit")
    if cert and isinstance(cert.get("short_circuit"), dict):
        sc = cert["short_circuit"]
        print(f"eligible    {sc.get('eligible')}")
        print(f"rule        {sc.get('rule')}")
        print(f"predicate   {sc.get('predicate')}")
    print()
    print("1. playbook  https://intrikata.com/onboard-grok.md")
    print("2. cert      https://intrikata.com/data/grok-onboard.json")
    print("3. skills    https://praxis-stack.pages.dev/pkg/praxis-stack-skills.zip")
    print("             py install.py --harness grok  →  ~/.grok/skills")
    print("4. optional  py intrikata.py provision <handle>  (minimum graphs only)")
    print("5. invoke    /megapraxis or 'orient me' — filesystem-fallback is designed")
    print()
    print("do not: auto-swarm · require MCP · treat traps #4/#5 as blockers on pure orientation")
    print("leave short-circuit when user asks audit/swarm/trap/bridges or needs live GraphDoc")
    print("sibling: https://praxis-stack.pages.dev/onboard-grok.md")


def cmd_onboard_codex(args):
    """Print the Codex short-circuit path; prefer live cert when reachable."""
    cert = None
    try:
        cert = fetch(CODEX_ONBOARD_URL, timeout=6)
        print(f"source: published cert ({cert.get('generated_at', '?')})")
    except Exception as exc:
        print(f"source: local fallback (cert fetch failed: {exc})")
    verdict = (cert or {}).get("verdict") or ((cert or {}).get("swarm_invocation") or {}).get(
        "verdict", "unverified"
    )
    print("OpenAI Codex — intrikata onboard short-circuit")
    print(f"verdict     {verdict}")
    print("class       onboarding-short-circuit")
    if cert and isinstance(cert.get("short_circuit"), dict):
        sc = cert["short_circuit"]
        print(f"eligible    {sc.get('eligible')}")
        print(f"rule        {sc.get('rule')}")
        print(f"predicate   {sc.get('predicate')}")
    if cert and isinstance(cert.get("runtime_closure"), dict):
        runtime = cert["runtime_closure"]
        print(f"runtime     {len(runtime.get('fresh_project_witnesses', []))} fresh-project witnesses")
    print()
    print("1. marketplace  codex plugin marketplace add https://praxis-stack.pages.dev/git/praxis-stack.git")
    print("2. plugin       codex plugin add praxis@praxis-stack")
    print("3. restart      close and reopen Codex once; verify the running task registry")
    print("4. invoke       $praxis:megapraxis in any project")
    print("5. optional     py intrikata.py provision <handle>  (minimum graphs only)")
    print()
    print("playbook  https://intrikata.com/onboard-codex.md")
    print("cert      https://intrikata.com/data/codex-onboard.json")
    print("rollback  codex plugin remove praxis@praxis-stack")
    print("          codex plugin marketplace remove praxis-stack")
    print("scope     two instruction skills; no bundled MCP, hooks, connector, credentials, or project writer")


def cmd_recover(args):
    if args.code:
        r = api_post(
            "/api/recover/verify",
            {"contact": args.contact, "code": args.code, "regenerate_recovery_key": True},
        )
        if r.get("error"):
            sys.exit(f"intrikata: {r['error']}")
        if not r.get("handles"):
            print("no instances claimed by that contact")
            return
        print("claimed instances:")
        for h in r["handles"]:
            print(f"  {h['handle']:24}{h['instance_id']}")
        for rk in r.get("recovery_keys") or []:
            path = _write_recovery_file(rk)
            if path:
                print(f"new recovery key written: {path}")
        if r.get("note"):
            print(r["note"])
        print("re-derive any with: intrikata provision <handle>  (derivation is deterministic)")
    else:
        r = api_post("/api/recover/start", {"contact": args.contact})
        if r.get("error"):
            sys.exit(f"intrikata: {r['error']}")
        print(f"code sent via {r['channel']} — complete with:")
        print(f"  intrikata recover {args.contact} -c <code>")
        print("  (MFA-complete handles get a new downloadable recovery key file)")


def main():
    # Windows consoles default to cp1252; graph names carry real Unicode.
    for stream in (sys.stdout, sys.stderr):
        try:
            stream.reconfigure(encoding="utf-8", errors="replace")
        except (AttributeError, ValueError):
            pass
    p = argparse.ArgumentParser(prog="intrikata", description="CLI for the praxis stack")
    sub = p.add_subparsers(dest="cmd", required=True)
    sub.add_parser("status", help="stack status")
    g = sub.add_parser("graphs", help="graph inventory")
    g.add_argument("--all", action="store_true")
    sub.add_parser("coverage", help="M/G/S/MGS coverage")
    t = sub.add_parser("traps", help="trap roll-call")
    t.add_argument("-v", "--verbose", action="store_true")
    sub.add_parser("bridges", help="bridge manifest")
    sub.add_parser("timeline", help="recent cycle verdicts")
    sub.add_parser("onboard-grok", help="Grok Build short-circuit onboard path (no MCP/swarm)")
    sub.add_parser("grok", help="alias of onboard-grok")
    sub.add_parser("onboard-codex", help="Codex install-once short-circuit onboard path")
    sub.add_parser("codex", help="alias of onboard-codex")
    pv = sub.add_parser("provision", help="download isolated minimal graph bundle (local import)")
    pv.add_argument("handle", help="your instance handle (e.g. an email or name)")
    pv.add_argument("-o", "--out", help="output file (default intrikata-<instance>.json)")
    bs = sub.add_parser("bootstrap", help="complete CF D1 minimal Pharosiraptor operant for handle")
    bs.add_argument("handle")
    op = sub.add_parser("operant", help="probe live CF operant health + node search")
    op.add_argument("handle")
    op.add_argument("--q", default="Swarm::", help="node name substring (default Swarm::)")
    lk = sub.add_parser("link", help="claim a handle with an email or E.164 phone (sends OTP)")
    lk.add_argument("handle")
    lk.add_argument("contact", help="email or +15551234567")
    vf = sub.add_parser("verify", help="complete a link with the OTP code")
    vf.add_argument("handle")
    vf.add_argument("contact")
    vf.add_argument("code")
    vf.add_argument("--rkey", action="store_true", help="rotate recovery key when MFA is complete")
    rc = sub.add_parser("recover", help="list handles claimed by a contact (sends OTP; -c to complete)")
    rc.add_argument("contact")
    rc.add_argument("-c", "--code", help="the OTP code you received")
    rk = sub.add_parser("recover-key", help="reclaim handle with offline recovery key; issues a new key file")
    rk.add_argument("--key", help="ikrec_v1_… recovery key string")
    rk.add_argument("--file", help="path to downloaded recovery key .txt")
    rk.add_argument("--no-rotate", action="store_true", help="do not issue a new recovery key")
    lm = sub.add_parser("link-mtls", help="4th MFA factor: issue device mTLS cert (live CF tunnel required)")
    lm.add_argument("handle")
    lm.add_argument("--session", required=True, help="local-bridge session_id (from console bridge)")
    lm.add_argument("--rotate", action="store_true", help="replace existing mTLS cert")
    lm.add_argument("--enroll-token", help="enroll token from recent factor verify (required to rotate)")
    rm = sub.add_parser("recover-mtls", help="prove mTLS device cert (challenge-response)")
    rm.add_argument("handle")
    rm.add_argument("--file", required=True, help="path to intrikata-mtls-<handle>.pem")
    args = p.parse_args()
    dispatch = {
        "status": cmd_status, "graphs": cmd_graphs, "coverage": cmd_coverage,
        "traps": cmd_traps, "bridges": cmd_bridges, "timeline": cmd_timeline,
        "onboard-grok": cmd_onboard_grok, "grok": cmd_onboard_grok,
        "onboard-codex": cmd_onboard_codex, "codex": cmd_onboard_codex,
        "provision": cmd_provision, "bootstrap": cmd_bootstrap, "operant": cmd_operant,
        "link": cmd_link, "verify": cmd_verify,
        "recover": cmd_recover, "recover-key": cmd_recover_key,
        "link-mtls": cmd_link_mtls, "recover-mtls": cmd_recover_mtls,
    }
    dispatch[args.cmd](args)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        sys.exit(130)
    except Exception as exc:
        print(f"intrikata: {exc}", file=sys.stderr)
        sys.exit(1)
