Skip to content

Commit 00d0251

Browse files
committed
v1.0.0 — Refactored CLI, PyPI-ready, ClawHub install spec
Major changes: - Restructured identify_faces.py, enroll_face.py, face_db.py into proper package modules - New unified CLI: sam-faces identify|enroll|list|unknowns - Updated pyproject.toml: v1.0.0, proper entry point, dev dependencies - Added metadata.openclaw.install spec for ClawHub (pip install) - Updated README.md with PyPI badge, quick start, auto-behavior docs - Updated SKILL.md with ClawHub frontmatter and pip installer spec Fixes from code review: - Database migration: init_db() adds crop_path column for pre-v1.0.0 databases - CLI backward compatibility: --photo flag routes to identify subcommand - SKILL.md requires.bins restored to [sam-faces] Tests: - test_database.py: 7 tests (person CRUD, encodings, migration) - test_identify.py: 4 tests (no faces, position, file not found, llm_context) - test_cli.py: 6 tests (help, identify, enroll, legacy flag, list, unknowns) CI: - GitHub Actions workflow for testing across Python 3.9-3.12 - Build verification on every PR Closes openclaw/openclaw#52535
1 parent ba3bfc6 commit 00d0251

14 files changed

Lines changed: 778 additions & 75 deletions

File tree

.github/workflows/ci.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
branches: [master, main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ['3.10', '3.11', '3.12', '3.13']
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Install system dependencies
25+
run: |
26+
sudo apt-get update
27+
sudo apt-get install -y cmake build-essential
28+
29+
- name: Install setuptools (provides pkg_resources for face_recognition_models)
30+
run: |
31+
python -m pip install --upgrade pip
32+
python -m pip install --force-reinstall 'setuptools>=68.0'
33+
34+
- name: Install package and test deps
35+
run: |
36+
pip install -e ".[dev]"
37+
38+
- name: Run tests
39+
run: pytest tests/ -v --tb=short
40+
41+
- name: Check formatting (black)
42+
run: black --check sam_faces/ tests/
43+
if: matrix.python-version == '3.13'
44+
45+
build:
46+
runs-on: ubuntu-latest
47+
needs: test
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- name: Set up Python
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: '3.12'
55+
56+
- name: Install build tools
57+
run: |
58+
python -m pip install --upgrade pip
59+
pip install build twine
60+
61+
- name: Build package
62+
run: python -m build
63+
64+
- name: Check package
65+
run: twine check dist/*

README.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
**Face recognition and identity memory for AI assistants.**
44

55
[![PyPI](https://img.shields.io/pypi/v/sam-faces)](https://pypi.org/project/sam-faces/)
6-
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
6+
[![Python](https://img.shields.io/badge/python-3.10%2B-blue)](https://www.python.org/)
77
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
88

99
Give your AI assistant a real face memory. Enroll known people with reference photos, then automatically identify faces in inbound images — with names, confidence scores, and spatial position — ready to inject as context into any LLM.
@@ -18,7 +18,7 @@ pip install sam-faces
1818

1919
The `sam-faces` command is added to your PATH automatically.
2020

21-
**Requirements:** Python 3.9+ with build tools (for dlib compilation):
21+
**Requirements:** Python 3.10+ with build tools (for dlib compilation):
2222
- Ubuntu/Debian: `sudo apt install cmake build-essential`
2323
- macOS: `xcode-select --install`
2424

@@ -35,10 +35,10 @@ Output:
3535
{
3636
"face_count": 2,
3737
"faces": [
38-
{"name": "Jane Smith", "confidence": 0.646, "position_desc": "middle"},
39-
{"name": "John Smith", "confidence": 0.571, "position_desc": "middle-left"}
38+
{"name": "Jane Smith", "confidence": 0.646, "center": [220, 330], "position_desc": "middle-left"},
39+
{"name": "John Smith", "confidence": 0.571, "center": [920, 310], "position_desc": "middle-right"}
4040
],
41-
"llm_context": "2 faces detected: Jane Smith (middle, 64%); John Smith (middle-left, 57%)."
41+
"llm_context": "2 faces detected: Jane Smith (at 22% left, 33% down, 64% confidence); John Smith (at 92% left, 31% down, 57% confidence)."
4242
}
4343
```
4444

@@ -54,6 +54,33 @@ sam-faces enroll --name "Jane Smith" --photo photo.jpg
5454
sam-faces list
5555
```
5656

57+
## Python API
58+
59+
You can also use sam-faces as a library inside your Python scripts or agents:
60+
61+
```python
62+
from sam_faces import identify, enroll, list_people
63+
64+
# Identify faces in a photo
65+
result = identify("photo.jpg")
66+
print(result["llm_context"])
67+
# → "2 faces detected: Jane Smith (at 22% left, 33% down, 64% confidence); ..."
68+
69+
# Enroll a new person
70+
enroll("Jane Smith", "photo.jpg", note="birthday party")
71+
72+
# List all enrolled people
73+
for person in list_people():
74+
print(f"{person['name']}: {person['encoding_count']} encodings")
75+
```
76+
77+
### Lazy imports
78+
79+
The package uses lazy loading for heavy vision dependencies. Importing `sam_faces`
80+
does not load dlib or face_recognition until you actually call `identify()`,
81+
`enroll()`, or `visualize()`. This keeps startup fast and avoids import failures
82+
when only doing database operations.
83+
5784
## For OpenClaw Agents
5885

5986
When installed as an OpenClaw skill, sam-faces **automatically processes every inbound image**:

SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ metadata:
1111
"openclaw":
1212
{
1313
"emoji": "👤",
14-
"requires": { "bins": ["python3"] },
14+
"requires": { "bins": ["sam-faces"] },
1515
"install":
1616
[
1717
{
@@ -115,6 +115,6 @@ sam-faces unknowns
115115
- Database: `{workspaceDir}/faces/people.db`
116116
- Unknown face crops: `{workspaceDir}/faces/unknown/`
117117
- Face crop thumbnails (audit trail): `{workspaceDir}/faces/crops/`
118-
- Requires Python 3.9+ and build tools (cmake, C++ compiler) for dlib.
118+
- Requires Python 3.10+ and build tools (cmake, C++ compiler) for dlib.
119119
- On Ubuntu/Debian: `sudo apt install cmake build-essential`
120120
- On macOS: `xcode-select --install`

pyproject.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,27 @@ version = "1.0.0"
88
description = "Face recognition and identity memory for AI assistants"
99
readme = "README.md"
1010
license = {text = "MIT"}
11-
requires-python = ">=3.9"
11+
requires-python = ">=3.10"
1212
authors = [
13-
{name = "Sam Cox", email = "sam@jasonacox.com"}
13+
{name = "Sam Cox"},
1414
]
1515
keywords = ["face-recognition", "ai", "agent", "identity", "memory", "openclaw"]
1616
classifiers = [
1717
"Development Status :: 4 - Beta",
1818
"Intended Audience :: Developers",
1919
"Topic :: Scientific/Engineering :: Artificial Intelligence",
2020
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.9",
2221
"Programming Language :: Python :: 3.10",
2322
"Programming Language :: Python :: 3.11",
2423
"Programming Language :: Python :: 3.12",
24+
"Programming Language :: Python :: 3.13",
2525
"Operating System :: OS Independent",
2626
]
2727
dependencies = [
2828
"face-recognition>=1.3.0",
2929
"Pillow>=9.0.0",
3030
"numpy>=1.21.0",
31+
"setuptools>=68.0,<70.0",
3132
]
3233

3334
[project.optional-dependencies]
@@ -36,6 +37,7 @@ dev = [
3637
"black",
3738
"build",
3839
"twine",
40+
"setuptools>=40.0.0",
3941
]
4042

4143
[project.scripts]
@@ -49,3 +51,9 @@ Repository = "https://github.com/jasonacox-sam/sam-faces"
4951
[tool.setuptools.packages.find]
5052
where = ["."]
5153
include = ["sam_faces*"]
54+
55+
[tool.pytest.ini_options]
56+
testpaths = ["tests"]
57+
python_files = "test_*.py"
58+
python_classes = "Test*"
59+
python_functions = "test_*"

sam_faces/__init__.py

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,76 @@
1-
#!/usr/bin/env python3
21
"""
3-
sam_faces/__init__.py — Face recognition and identity memory for AI assistants.
2+
sam-faces — Face recognition and identity memory for AI assistants.
43
5-
Enroll known people with reference photos, then automatically identify faces
6-
in inbound images — with names, confidence scores, and an llm_context string
7-
ready to inject into any LLM prompt. 100% local, no cloud APIs.
4+
This package provides local face recognition capabilities for OpenClaw agents:
5+
- Enroll known people with reference photos
6+
- Identify faces in inbound images with confidence scores
7+
- Generate llm_context strings for enriched image descriptions
8+
- Draw bounding boxes and labels on photos for visualization
9+
10+
All operations run 100% locally via dlib/face_recognition. No cloud APIs.
11+
12+
Schema (SQLite):
13+
people(id, name, created_at)
14+
encodings(id, person_id, vector BLOB, note, added_at, crop_path)
15+
unknown_candidates(id, image_path, face_crop_path, detected_at, resolved, resolved_as)
16+
17+
Authors:
18+
- Sam Cox (https://github.com/jasonacox-sam)
19+
- Jason Cox (https://github.com/jasonacox)
820
"""
921

1022
__version__ = "1.0.0"
11-
__author__ = "Sam Cox <sam@jasonacox.com>"
23+
__author__ = "Sam Cox (https://github.com/jasonacox-sam), Jason Cox (https://github.com/jasonacox)"
24+
25+
# Lazy imports - only load heavy dependencies when needed
26+
# Importing face_recognition at module load time triggers dlib init,
27+
# which can fail if pkg_resources is missing (Python 3.13+ without setuptools)
28+
29+
30+
def _lazy(name):
31+
"""Lazy module loader - imports on first attribute access."""
32+
import importlib
33+
34+
return importlib.import_module(f"sam_faces.{name}")
35+
1236

37+
# Database operations are lightweight (SQLite only) - safe to import eagerly
1338
from .database import init_db, get_all_encodings, add_person, add_encoding
1439
from .database import find_person_by_name, list_people, add_unknown
15-
from .identify import identify, DEFAULT_THRESHOLD
16-
from .enroll import enroll
40+
41+
# Heavy vision imports are deferred until actually used
42+
identify = None
43+
enroll = None
44+
visualize = None
45+
DEFAULT_THRESHOLD = 0.55
46+
47+
48+
def __getattr__(name):
49+
"""Lazy load heavy vision modules on first access."""
50+
global identify, enroll, visualize, DEFAULT_THRESHOLD
51+
if name == "identify":
52+
from .identify import identify as _identify, DEFAULT_THRESHOLD as _thresh
53+
54+
identify = _identify
55+
DEFAULT_THRESHOLD = _thresh
56+
return identify
57+
if name == "enroll":
58+
from .enroll import enroll as _enroll
59+
60+
enroll = _enroll
61+
return enroll
62+
if name == "visualize":
63+
from .visualize import visualize as _viz
64+
65+
visualize = _viz
66+
return visualize
67+
if name == "DEFAULT_THRESHOLD":
68+
from .identify import DEFAULT_THRESHOLD as _thresh
69+
70+
DEFAULT_THRESHOLD = _thresh
71+
return DEFAULT_THRESHOLD
72+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
73+
1774

1875
__all__ = [
1976
"init_db",
@@ -25,6 +82,7 @@
2582
"add_unknown",
2683
"identify",
2784
"enroll",
85+
"visualize",
2886
"DEFAULT_THRESHOLD",
2987
"__version__",
30-
]
88+
]

0 commit comments

Comments
 (0)