From 072ee3f36cd737b49c615094be35748bd49f3122 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 26 Apr 2026 21:03:19 -0700 Subject: [PATCH] chore: scaffold Merlin Phase A repo --- .gitignore | 10 ++++++ merlin/__init__.py | 0 merlin/actions/__init__.py | 0 merlin/config.py | 56 +++++++++++++++++++++++++++++++++ merlin/features/__init__.py | 0 pyproject.toml | 22 +++++++++++++ tests/__init__.py | 0 tests/test_actions/__init__.py | 0 tests/test_features/__init__.py | 0 9 files changed, 88 insertions(+) create mode 100644 .gitignore create mode 100644 merlin/__init__.py create mode 100644 merlin/actions/__init__.py create mode 100644 merlin/config.py create mode 100644 merlin/features/__init__.py create mode 100644 pyproject.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_actions/__init__.py create mode 100644 tests/test_features/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6330d16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__/ +*.pyc +*.pyo +.env +.merlin/ +models/*.pkl +dist/ +*.egg-info/ +node_modules/ +app/dist/ diff --git a/merlin/__init__.py b/merlin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merlin/actions/__init__.py b/merlin/actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/merlin/config.py b/merlin/config.py new file mode 100644 index 0000000..3994f51 --- /dev/null +++ b/merlin/config.py @@ -0,0 +1,56 @@ +""" +MerlinConfig — load and save user gesture-to-action mappings. + +Config file: ~/.merlin/config.yaml +If the file does not exist, defaults are used and written on first save. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field, asdict +from pathlib import Path +from typing import Optional + +import yaml + +CONFIG_PATH = Path.home() / ".merlin" / "config.yaml" + +DEFAULT_MAPPINGS: dict[str, str] = { + "left_blink": "left_click", + "right_blink": "right_click", + "both_blink": "double_click", + "head_nod": "key_enter", + "head_shake": "key_escape", + "head_tilt_left": "scroll_up", + "head_tilt_right": "scroll_down", + "open_palm": "key_space", + "pinch": "drag_start", +} + + +@dataclass +class MerlinConfig: + mappings: dict[str, str] = field(default_factory=lambda: dict(DEFAULT_MAPPINGS)) + confidence_threshold: float = 0.80 + camera_device: int = 0 + dwell_ms: int = 800 + blink_threshold: float = 0.20 + + @classmethod + def load(cls, path: Path = CONFIG_PATH) -> MerlinConfig: + if not path.exists(): + return cls() + with path.open() as f: + data = yaml.safe_load(f) or {} + return cls( + mappings=data.get("mappings", DEFAULT_MAPPINGS), + confidence_threshold=float(data.get("confidence_threshold", 0.80)), + camera_device=int(data.get("camera_device", 0)), + dwell_ms=int(data.get("dwell_ms", 800)), + blink_threshold=float(data.get("blink_threshold", 0.20)), + ) + + def save(self, path: Path = CONFIG_PATH) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w") as f: + yaml.safe_dump(asdict(self), f, default_flow_style=False) diff --git a/merlin/features/__init__.py b/merlin/features/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff952c2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "merlin" +version = "0.1.0" +description = "Merlin — BCI and alternative input training harness (BSL 1.1)" +requires-python = ">=3.11" +dependencies = [ + "circuitforge-core[gestures-mediapipe]", + "fastapi>=0.110", + "uvicorn[standard]>=0.29", + "scikit-learn>=1.4", + "joblib>=1.3", + "pyyaml>=6.0", + "pyautogui>=0.9", +] + +[tool.setuptools.packages.find] +where = ["."] +include = ["merlin*"] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_actions/__init__.py b/tests/test_actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_features/__init__.py b/tests/test_features/__init__.py new file mode 100644 index 0000000..e69de29