# SecondBrain — (c) 2026 Atlas Corporation. All Rights Reserved. Proprietary; see LICENSE. """Connector framework — the "senses" made concrete. Each connector authenticates to an app, pulls data, and feeds it through the SAME scrub + dedup path as the core engine (`brain._stage`). Consequences: * connector data is credential-scrubbed too (secrets never land in brain.db); * app logins live only in the encrypted vault (see vault.py), never in brain.db; * the engine (brain.py) stays completely connector-agnostic. """ import importlib, os, sys, pkgutil REGISTRY = {} def register(cls): REGISTRY[cls.id] = cls return cls class Field: def __init__(self, key, label, secret=False, default="", required=True): self.key, self.label, self.secret = key, label, secret self.default, self.required = default, required class Connector: id = "" # short stable id (vault key) name = "" # display name blurb = "" # one-line description fields = [] # list[Field] the wizard collects def test(self, cfg): # verify creds; raise on failure; return status str return "ok" def sync(self, cfg): # pull -> feed(); return new-chunk count raise NotImplementedError def _brain(): """Import the sibling engine regardless of CWD.""" here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if here not in sys.path: sys.path.insert(0, here) import brain return brain def feed(items, source): """Stage connector items through the engine's scrub+dedup. items: iterable of dict(text[, project, path, ref, ts, role]). Returns new-chunk count. Does NOT embed (run brain.embed() after a sync batch).""" brain = _brain() conn = brain.db(); n = 0 for it in items: if not it.get("text"): continue n += brain._stage(conn, source, it.get("project", ""), it.get("path", ""), it.get("ref", ""), it.get("ts", ""), it.get("role", ""), it["text"]) conn.commit(); conn.close() return n def load_all(): """Import every connector module so it self-registers.""" pkgdir = os.path.dirname(os.path.abspath(__file__)) for m in pkgutil.iter_modules([pkgdir]): if m.name != "base" and not m.name.startswith("_") and m.name != "vault": importlib.import_module(f"connectors.{m.name}") return REGISTRY