Initial Commit

This commit is contained in:
2026-06-23 23:54:37 +00:00
commit 3c9742ff87
38 changed files with 4792 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
35;13;39M35;18;41M35;22;43M35;25;44M35;32;46M35;33;47M35;36;48M35;38;49M0;38;49M0;38;49m65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M65;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M64;38;49M0;38;49M0;38;49m35;37;48M35;36;48M35;36;47M35;34;46M35;33;45M35;32;43M35;32;42M35;32;41M35;32;40M35;32;37M35;35;36M35;39;35M35;45;34M35;53;34M35;57;34M35;64;35M35;70;36M35;72;36M35;73;37M35;74;37M"""
Configuration for ctxd — context daemon.
"""
import os
from pathlib import Path
# Default home directory (~/.ctx) — overridable via CTXD_HOME env var
DEFAULT_HOME = Path(os.environ.get("CTXD_HOME", Path.home() / ".ctx"))
# Defaults for ctxd.yaml
DEFAULT_CONFIG = {
"server": {
"host": "0.0.0.0",
"port": 9091,
},
"snapshots": {
"min_keep": 5,
"max_keep": 25,
},
"auth": {
"enabled": False,
"api_key": "",
},
"seed": {
"admin_user": "admin",
"admin_display": "Administrator",
"service_user": "hermes-gateway",
"service_display": "Hermes Agent",
},
}
class CtxConfig:
"""Holds resolved paths and config for a ctxd runtime."""
def __init__(self, home: Path | str | None = None, config: dict | None = None):
resolved = Path(home) if home else DEFAULT_HOME
self.home = resolved.resolve()
self._cfg = config or dict(DEFAULT_CONFIG)
# ── Directory layout ──────────────────────────────────────────
@property
def db_path(self) -> Path:
return self.home / "ctxd.db"
@property
def snapshots_dir(self) -> Path:
return self.home / "snapshots"
@property
def projects_dir(self) -> Path:
return self.home / "projects"
@property
def user_dir(self) -> Path:
return self.home / "users"
@property
def config_path(self) -> Path:
return self.home / "ctxd.yaml"
# ── Config accessors ──────────────────────────────────────────
@property
def host(self) -> str:
return self._cfg.get("server", {}).get("host", "127.0.0.1")
@property
def port(self) -> int:
return self._cfg.get("server", {}).get("port", 9091)
@property
def min_snapshots(self) -> int:
return self._cfg.get("snapshots", {}).get("min_keep", 5)
@property
def max_snapshots(self) -> int:
return self._cfg.get("snapshots", {}).get("max_keep", 25)
# ── Auth ──────────────────────────────────────────────────────
@property
def auth_enabled(self) -> bool:
return self._cfg.get("auth", {}).get("enabled", False)
@property
def api_key(self) -> str:
return self._cfg.get("auth", {}).get("api_key", "")
# ── Bootstrap ─────────────────────────────────────────────────
def ensure_dirs(self):
"""Create all required directories if they don't exist."""
for d in [self.home, self.snapshots_dir, self.projects_dir, self.user_dir]:
d.mkdir(parents=True, exist_ok=True)
@classmethod
def from_home(cls, home: Path | str | None = None) -> "CtxConfig":
"""Load from ctxd.yaml if it exists, otherwise use defaults."""
home = Path(home).resolve() if home else DEFAULT_HOME
cfg_path = home / "ctxd.yaml"
if cfg_path.exists():
import yaml
with open(cfg_path) as f:
data = yaml.safe_load(f) or {}
return cls(home=str(home), config=data)
return cls(home=str(home))
def save(self):
"""Write config to ctxd.yaml."""
import yaml
self.ensure_dirs()
with open(self.config_path, "w") as f:
yaml.dump(self._cfg, f, default_flow_style=False, sort_keys=False)