face-cli 0.6.1__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {face_cli-0.6.1 → face_cli-0.7.1}/CHANGELOG.md +75 -0
- face_cli-0.7.1/CLAUDE.md +325 -0
- face_cli-0.7.1/PKG-INFO +113 -0
- face_cli-0.7.1/README.md +96 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/__init__.py +26 -2
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/learn.py +17 -8
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/overview.py +9 -1
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/whoami.py +2 -2
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/explain/catalog.py +9 -6
- {face_cli-0.6.1 → face_cli-0.7.1}/pyproject.toml +2 -2
- {face_cli-0.6.1 → face_cli-0.7.1}/tests/test_cli.py +24 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/uv.lock +10 -10
- face_cli-0.6.1/CLAUDE.md +0 -28
- face_cli-0.6.1/PKG-INFO +0 -76
- face_cli-0.6.1/README.md +0 -59
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/agent-config/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/challenge/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/deviate/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/recall/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/recall/scripts/recall.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/remember/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/remember/scripts/remember.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/scope/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/summarize-delivery/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/think/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/think/scripts/think.sh +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/version-bump/SKILL.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.claude/skills.local.yaml.example +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.flake8 +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.github/workflows/publish.yml +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.github/workflows/tests.yml +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.gitignore +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/.markdownlint-cli2.yaml +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/AGENTS.colleague.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/LICENSE +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/culture.yaml +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/docs/skill-sources.md +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/__init__.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/__main__.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/__init__.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/cli.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/doctor.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_commands/explain.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_errors.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/cli/_output.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/face_cli/explain/__init__.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/sonar-project.properties +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/tests/__init__.py +0 -0
- {face_cli-0.6.1 → face_cli-0.7.1}/tests/test_cli_introspection.py +0 -0
|
@@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.1] - 2026-07-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`--json` is accepted before the verb, not only after it** — `face --json
|
|
13
|
+
whoami` used to fail with `unrecognized arguments: --json` while `face whoami
|
|
14
|
+
--json` worked. Flag-before-verb is a common idiom, and an agent-first CLI
|
|
15
|
+
that rejects it is a trap. The root parser now declares `--json`, and
|
|
16
|
+
`main()` re-asserts it after `parse_args` from the existing raw-argv scan:
|
|
17
|
+
each subparser re-declares `--json` with its own `False` default, which
|
|
18
|
+
silently overwrites a leading flag during sub-parsing. Both orders now
|
|
19
|
+
produce identical output, pinned by a parametrized test.
|
|
20
|
+
- **`face --json` with no verb emits the machine-readable command map** rather
|
|
21
|
+
than an error — the JSON analogue of printing help. It reuses `learn`'s
|
|
22
|
+
payload builder (promoted from `_as_json_payload` to `as_json_payload`), so
|
|
23
|
+
there is one command map, not two.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **The CLI's own introspection text now describes this agent** — `face --help`,
|
|
28
|
+
`face learn`, `face explain`, and `face overview` still called the repo "a
|
|
29
|
+
clonable template for AgentCulture mesh agents" and gave clone/rename
|
|
30
|
+
instructions, contradicting the docs 0.7.0 rewrote. Because `learn` and
|
|
31
|
+
`explain` are explicitly the machine-readable teaching surface, that mismatch
|
|
32
|
+
misinformed every agent consuming them, not just human readers. All four now
|
|
33
|
+
state the domain (the expressive output side of a face; recognition belongs to
|
|
34
|
+
the sibling) and the honest status (scaffold — no renderer, no gaze primitive,
|
|
35
|
+
no render verbs yet), and `overview` gained a `Status` section carrying the
|
|
36
|
+
same. The leftover clone/rename framing is also gone from the `whoami` and
|
|
37
|
+
`overview` module docstrings. The `face` / `face-cli` naming split is
|
|
38
|
+
untouched: the console script stays `face` and every help / `learn` /
|
|
39
|
+
`explain` string stays `face-cli`, matching argparse's `prog`.
|
|
40
|
+
- **Package metadata no longer advertises capability that was never requested**
|
|
41
|
+
— `pyproject.toml`'s description promised a "gaze **and expression** surface"
|
|
42
|
+
while `README.md` says "gaze surface" and the build brief records that
|
|
43
|
+
expression was *not* asked for. Published metadata is public-facing (it is
|
|
44
|
+
what PyPI shows), so the conflicting claim was narrowed to match the README.
|
|
45
|
+
This removes the claim rather than settling the scope question, which stays
|
|
46
|
+
open in `CLAUDE.md`; the GitHub repo description, set at provisioning, still
|
|
47
|
+
carries the old wording and should follow whichever way the decision lands.
|
|
48
|
+
|
|
49
|
+
- **Public-facing descriptions no longer read as if the renderer exists** —
|
|
50
|
+
`pyproject.toml`'s description (what PyPI renders, and `publish.yml` pushes a
|
|
51
|
+
release on every merge to `main`) and the root `argparse` description both
|
|
52
|
+
described a working browser-rendered face in the present tense, while
|
|
53
|
+
`learn` / `explain` / `overview` said "scaffold, no renderer". Both now carry
|
|
54
|
+
the same qualifier. This also makes 0.7.1's own claim about `face --help`
|
|
55
|
+
true — it stated the honest status was there before the root description
|
|
56
|
+
actually carried it.
|
|
57
|
+
|
|
58
|
+
The first two items were raised by Qodo's review of
|
|
59
|
+
[#2](https://github.com/agentculture/face-cli/pull/2) (review comments
|
|
60
|
+
`3646920415`, `3646920422`), which merged before the fixes landed — hence a
|
|
61
|
+
follow-up release rather than an amendment. The remaining items came from
|
|
62
|
+
Qodo's review of [#3](https://github.com/agentculture/face-cli/pull/3) (review
|
|
63
|
+
comments `3647009663`, `3647009668`).
|
|
64
|
+
|
|
65
|
+
## [0.7.0] - 2026-07-24
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **`CLAUDE.md` initialized from the seed** (`/init`) — replaces the bootstrap placeholder with a real runtime prompt: what the repo is *today* (a scaffold, no renderer on disk), the deliberate `face` command / `face_cli` import / `face-cli` dist split and why it must not be "fixed", the four CLI contracts the rubric gate enforces (registration via `register(sub)`, the `CliError` + `_json_hint` error path, the strict stdout/stderr split, the tuple-keyed `explain` catalog), the five places a new verb must touch, and the decided-vs-open state of the face work carried over from build brief agentculture/face-cli#1.
|
|
70
|
+
- **Face-recognition acceptance constraint** recorded in `CLAUDE.md` and `README.md` — the rendered face must survive the same OpenCV pipeline sibling `face-recognition-cli` extracts from `reachy-mini-cli` (`reachy/vision/face.py`): YuNet detection above the default `0.6` score threshold, and a stable SFace 128-dim embedding across the gaze range. This settles the brief's stylised-vs-realistic question at "realistic enough that a detector fires" rather than photorealism, and replaces the untestable "it looks 3D" with an assertion CI can make — a face that stops being detectable at extreme yaw has failed the illusion in a way a screenshot diff would miss.
|
|
71
|
+
- **Dependency guidance for when the renderer lands** — the empty runtime-dependency set is a property of the scaffold, not a law: put a server/screenshot stack behind an extra, import it lazily inside functions, and surface absence as a clean `EXIT_ENV_ERROR` `CliError`, mirroring `reachy-mini-cli`'s `[vision]` pattern.
|
|
72
|
+
- **Template conventions restored** into `CLAUDE.md` after the scaffold flattened them — version-bump-every-PR, the `cicd` PR lane, the `ask-colleague` reflex, verbatim-vendored skills, the `../.worktrees.face-cli/<name>/` worktree convention, and in-repo public memory discipline (`/recall` before, `/remember` after).
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- **`README.md` rewritten for this agent** — it described the `culture-agent-template` clone procedure ("Make it your own", the ~100-place rename sweep) rather than face-cli. Now leads with the scaffold status, the two halves of the problem (depth illusion + pointable gaze), the decided browser render target with its three reasons, the face-recognition bar, and the lane boundary against `face-recognition-cli`.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **Wrong console command throughout `README.md`** — the quickstart said `uv run face-cli whoami` / `uv run face-cli learn`, which fail with `Failed to spawn: face-cli`. The installed entry point is `face` (`[project.scripts] face = "face_cli.cli:main"`); only argparse's `prog` is `face-cli`.
|
|
81
|
+
- **Stale identity claim in the seed** — it stated `culture.yaml` declares `backend: claude`, while the checked-in value is `colleague` (resident prompt `AGENTS.colleague.md`, which is what `doctor` and `steward doctor` actually verify).
|
|
82
|
+
|
|
8
83
|
## [0.6.1] - 2026-07-20
|
|
9
84
|
|
|
10
85
|
### Added
|
face_cli-0.7.1/CLAUDE.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
`face-cli` is the **expressive output side of a face**: a simulated face
|
|
8
|
+
rendered in the browser that reads as three-dimensional on a flat 2D screen and
|
|
9
|
+
can look in any direction — an agent-drivable gaze surface for robots, kiosks,
|
|
10
|
+
and screens.
|
|
11
|
+
|
|
12
|
+
**Status on disk today: scaffold only.** There is no renderer, no browser
|
|
13
|
+
asset, no gaze primitive, and no `serve` verb in this repo yet. What exists is
|
|
14
|
+
the `culture-agent-template` baseline — an agent-first CLI skeleton, a mesh
|
|
15
|
+
identity, the vendored skill kit, and CI. *The face is the work, and it has not
|
|
16
|
+
started.* Keep that distinction visible when you write docs: describe the repo
|
|
17
|
+
as it is, and mark anything else `(planned)` or put it under a roadmap heading.
|
|
18
|
+
|
|
19
|
+
The authoritative statement of the lane is the build brief,
|
|
20
|
+
[agentculture/face-cli#1](https://github.com/agentculture/face-cli/issues/1).
|
|
21
|
+
Read it before designing anything. This file summarizes the parts that are
|
|
22
|
+
already decided and the parts that are still open; the issue is the source of
|
|
23
|
+
truth and the place to renegotiate.
|
|
24
|
+
|
|
25
|
+
## Naming — deliberately decoupled, do not "fix"
|
|
26
|
+
|
|
27
|
+
| Thing | Value |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Console command | `face` |
|
|
30
|
+
| Import package | `face_cli` |
|
|
31
|
+
| PyPI distribution | `face-cli` |
|
|
32
|
+
| Argparse `prog` | `face-cli` |
|
|
33
|
+
|
|
34
|
+
The command is `face` but the import package is `face_cli` because a bare
|
|
35
|
+
`face` is a real published PyPI distribution (a `glom` dependency) — squatting
|
|
36
|
+
that top-level import would shadow it. This mismatch is intentional; leave it.
|
|
37
|
+
|
|
38
|
+
Practical consequence: the installed entry point is **`uv run face …`**, not
|
|
39
|
+
`uv run face-cli …`. The help text and `explain` catalog say `face-cli`
|
|
40
|
+
(the `prog`), and `explain` accepts both `face` and `face-cli` as the root key.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv sync # create/refresh .venv from uv.lock
|
|
46
|
+
uv run pytest -n auto # full suite (xdist, ~22 tests, <1s)
|
|
47
|
+
uv run pytest tests/test_cli.py::test_whoami_json -v # a single test
|
|
48
|
+
uv run pytest -n auto --cov=face_cli --cov-report=term # coverage (fail_under = 60)
|
|
49
|
+
uv run face whoami # identity from culture.yaml
|
|
50
|
+
uv run face learn --json # self-teaching prompt
|
|
51
|
+
uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Lint — CI runs all five and any one failing fails the `lint` job:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uv run black --check face_cli tests # line-length 100
|
|
58
|
+
uv run isort --check-only face_cli tests
|
|
59
|
+
uv run flake8 face_cli tests
|
|
60
|
+
uv run bandit -c pyproject.toml -r face_cli
|
|
61
|
+
markdownlint-cli2 "**/*.md" "#node_modules" "#.local" "#.claude/skills" "#.teken"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Version bump (required on every PR — see Conventions):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
python3 .claude/skills/version-bump/scripts/bump.py patch|minor|major
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Architecture — the CLI as it stands
|
|
71
|
+
|
|
72
|
+
The whole CLI is cited (cite-don't-import) from teken's `python-cli` reference,
|
|
73
|
+
which is why **the runtime package has zero third-party dependencies**;
|
|
74
|
+
`teken` is a dev dependency only. Four contracts hold it together, and the
|
|
75
|
+
rubric gate (`teken cli doctor . --strict`, 26 checks) enforces all of them:
|
|
76
|
+
|
|
77
|
+
- **Registration.** `face_cli/cli/__init__.py::_build_parser` imports each
|
|
78
|
+
module under `cli/_commands/` and calls its `register(sub)`. Every command
|
|
79
|
+
module owns its own parser, flags, and `set_defaults(func=…)`. Adding a verb
|
|
80
|
+
never touches the dispatcher.
|
|
81
|
+
- **Errors.** Every failure raises `CliError(code, message, remediation)`
|
|
82
|
+
(`cli/_errors.py`). `_dispatch` catches it, and wraps *any* other exception
|
|
83
|
+
into one, so a Python traceback can never reach stderr. `_CliArgumentParser`
|
|
84
|
+
overrides `argparse`'s `.error()` so parse failures take the same path and
|
|
85
|
+
exit `1` rather than argparse's default `2`. Because parse errors happen
|
|
86
|
+
before `args.json` exists, `main()` pre-scans raw argv for `--json` and
|
|
87
|
+
stashes it in the class-level `_json_hint` — that is why the flag is peeked
|
|
88
|
+
at twice, and it is load-bearing.
|
|
89
|
+
- **Streams.** `cli/_output.py` is the only writer: results to stdout,
|
|
90
|
+
errors and diagnostics to stderr, never mixed, in both text and JSON mode.
|
|
91
|
+
- **Explain.** `face_cli/explain/catalog.py` maps command-path *tuples* to
|
|
92
|
+
verbatim markdown. `tests/test_cli.py::test_every_catalog_path_resolves`
|
|
93
|
+
walks every key, so an entry that exists must resolve; the rubric separately
|
|
94
|
+
requires an entry for the root and a non-zero exit for a bogus path.
|
|
95
|
+
|
|
96
|
+
Two modules encode mesh identity rather than CLI mechanics:
|
|
97
|
+
|
|
98
|
+
- `cli/_commands/whoami.py` walks up from `__file__` (**not** the CWD) to find
|
|
99
|
+
this agent's own `culture.yaml`, and hand-parses the first agent block
|
|
100
|
+
without a YAML dependency — that is what keeps runtime deps empty. A wheel
|
|
101
|
+
install ships no `culture.yaml`, so it falls back to literal defaults.
|
|
102
|
+
- `cli/_commands/doctor.py` mirrors the two invariants `steward doctor`
|
|
103
|
+
verifies — prompt-file-present and backend-consistency — via the
|
|
104
|
+
`_PROMPT_FILE` map (`claude` → `CLAUDE.md`, `colleague` →
|
|
105
|
+
`AGENTS.colleague.md`, `acp` → `AGENTS.md`, `gemini` → `GEMINI.md`), plus a
|
|
106
|
+
skills-present check. It returns the rubric's
|
|
107
|
+
`{healthy, checks: [{id, passed, severity, message, remediation}]}` shape.
|
|
108
|
+
|
|
109
|
+
### Adding a verb or noun group
|
|
110
|
+
|
|
111
|
+
Five places, and the tests will catch you if you miss one:
|
|
112
|
+
|
|
113
|
+
1. A module in `face_cli/cli/_commands/` exposing `register(sub)`; add
|
|
114
|
+
`--json` to every parser you create.
|
|
115
|
+
2. A `register()` call in `_build_parser()`.
|
|
116
|
+
3. An entry in `face_cli/explain/catalog.py` keyed by the path tuple.
|
|
117
|
+
4. The command map inside `cli/_commands/learn.py` — both `_TEXT` and
|
|
118
|
+
`_as_json_payload()`. The rubric greps `learn` output for markers.
|
|
119
|
+
5. Tests in `tests/`.
|
|
120
|
+
|
|
121
|
+
Any noun group that gains action verbs must also expose `overview`
|
|
122
|
+
(`overview_cli_noun_exists`); descriptive verbs must not hard-fail on a bad
|
|
123
|
+
target path (`overview_graceful_on_bad_path` — see the ignored `target`
|
|
124
|
+
positional in `overview.py`).
|
|
125
|
+
|
|
126
|
+
### When the renderer lands
|
|
127
|
+
|
|
128
|
+
The empty-runtime-deps property is a feature of the scaffold, not a law of the
|
|
129
|
+
project — a browser renderer will need a server and screenshot tooling. Follow
|
|
130
|
+
the sibling pattern instead of adding hard top-level deps: put them behind an
|
|
131
|
+
extra (`[serve]`, `[vision]`), import them **lazily inside functions**, and
|
|
132
|
+
surface a missing dependency as a clean `CliError` with `EXIT_ENV_ERROR` (2)
|
|
133
|
+
pointing at the extra. `reachy-mini-cli`'s `reachy/vision/face.py` is the
|
|
134
|
+
reference implementation of that pattern in this workspace.
|
|
135
|
+
|
|
136
|
+
## The face work — what is decided
|
|
137
|
+
|
|
138
|
+
**The render target is the browser.** Decided by the operator; hold the line
|
|
139
|
+
when it gets inconvenient. Three reasons, all still true: no display server is
|
|
140
|
+
needed on a headless robot box or server; the screen showing the face need not
|
|
141
|
+
be the machine running the process; and a page can be screenshotted and diffed
|
|
142
|
+
in CI, which a native window effectively cannot. WebGL vs 2D canvas vs SVG is
|
|
143
|
+
an open technique choice — the operator specified the target, not the method.
|
|
144
|
+
|
|
145
|
+
**The rendered face must survive face recognition.** This is an operator
|
|
146
|
+
requirement layered on the brief: our virtual face should be recognizable *as a
|
|
147
|
+
face, and as a consistent identity*, by the same machinery that recognizes real
|
|
148
|
+
people. The concrete pipeline is the sibling `face-recognition-cli`'s, which
|
|
149
|
+
extracts `reachy-mini-cli`'s OpenCV engine (`reachy/vision/face.py`):
|
|
150
|
+
|
|
151
|
+
- **YuNet** (`face_detection_yunet_2023mar.onnx`) for detection — default
|
|
152
|
+
score threshold `0.6`, NMS `0.3`, `320x320` input, largest-face selection.
|
|
153
|
+
- **SFace** (`face_recognition_sface_2021dec.onnx`) for a 128-dim embedding,
|
|
154
|
+
taken after `alignCrop` on YuNet's five landmarks.
|
|
155
|
+
|
|
156
|
+
That has three consequences worth holding onto:
|
|
157
|
+
|
|
158
|
+
1. It effectively answers the brief's stylised-vs-realistic question. A face
|
|
159
|
+
abstract enough to be two floating eyes will not fire a YuNet detection at
|
|
160
|
+
`0.6`. The render needs the landmark geometry YuNet regresses — two eyes, a
|
|
161
|
+
nose tip, two mouth corners — in plausible proportion, with enough tonal
|
|
162
|
+
contrast to survive `alignCrop`. It does not need photorealism, and chasing
|
|
163
|
+
photorealism buys the uncanny valley; "passes a detector" is a much cheaper
|
|
164
|
+
and much more testable bar than "looks real".
|
|
165
|
+
2. It replaces "it looks 3D" with an assertion CI can actually make. Render at
|
|
166
|
+
a known gaze angle → YuNet detects exactly one face above threshold →
|
|
167
|
+
SFace's embedding stays cosine-close to an enrolled reference across the
|
|
168
|
+
whole gaze range. That gates *both* halves of the brief at once: a face that
|
|
169
|
+
stops being detectable at ±40° yaw has failed the 3D illusion in a way a
|
|
170
|
+
screenshot diff would not have caught.
|
|
171
|
+
3. It defines the composition with the sibling without merging the lanes.
|
|
172
|
+
`face-cli` renders; `face-recognition-cli` enrolls and matches; the loop
|
|
173
|
+
closes when the same identity comes back. That integration is explicit and
|
|
174
|
+
coordinated through issues (use the `communicate` skill), not a merge —
|
|
175
|
+
this repo does no recognition, and that one does no rendering. Note the
|
|
176
|
+
sibling is *also* a bare scaffold today, so the near-term testable path is
|
|
177
|
+
OpenCV's YuNet/SFace directly, behind an extra, with the sibling adopted as
|
|
178
|
+
soon as it has a surface.
|
|
179
|
+
|
|
180
|
+
### Lane boundaries with siblings
|
|
181
|
+
|
|
182
|
+
- `face-recognition-cli` — perception (identifies real people from frames).
|
|
183
|
+
Shares the subject, nothing else.
|
|
184
|
+
- `reachy-mini-cli` — a physical head that actually turns; the on-screen
|
|
185
|
+
counterpart to this. Plausible that a Reachy gets its face from here; agree
|
|
186
|
+
it with that agent rather than assuming.
|
|
187
|
+
- `storybook-cli` — also renders to a browser, but its output is a *document*;
|
|
188
|
+
this one is a *live surface*.
|
|
189
|
+
- `reterminal-cli` — e-paper; its refresh rate is hostile to continuous gaze.
|
|
190
|
+
Not a first target.
|
|
191
|
+
|
|
192
|
+
## The face work — what is still open
|
|
193
|
+
|
|
194
|
+
Decide these deliberately and **record the decision** (a `docs/` note plus a
|
|
195
|
+
CHANGELOG entry); do not let them get settled by accident in a commit:
|
|
196
|
+
|
|
197
|
+
- **How a one-shot CLI call moves an already-rendered face.** Served page plus
|
|
198
|
+
a control channel (websocket / SSE / polled state file), a state file the
|
|
199
|
+
page watches, or something else.
|
|
200
|
+
- **Whether there is a `serve` verb** that owns the page, with `look` /
|
|
201
|
+
`express` driving it — and what happens when nothing is serving: error, or
|
|
202
|
+
autostart.
|
|
203
|
+
- **The gaze primitive.** Yaw/pitch degrees, a normalized direction vector, a
|
|
204
|
+
screen-space point, or a named target. Whatever it is, an agent must be able
|
|
205
|
+
to compute it with no human in the loop.
|
|
206
|
+
- **Whether expression is in scope at all.** The operator asked for direction.
|
|
207
|
+
Expression is the obvious neighbour and was *not* requested — decide
|
|
208
|
+
explicitly rather than drifting into it. Published metadata no longer
|
|
209
|
+
pre-answers it: `pyproject.toml` used to advertise a "gaze **and expression**
|
|
210
|
+
surface" while nothing of the sort exists, so its description was narrowed to
|
|
211
|
+
match the README. That removes the claim, it does not settle the question —
|
|
212
|
+
the GitHub repo description (set at provisioning) still carries the old
|
|
213
|
+
wording and should follow whichever way the decision lands.
|
|
214
|
+
- **What `--json` returns for a verb whose real output is pixels.** A
|
|
215
|
+
screenshot path is one honest answer; the resolved gaze state is another.
|
|
216
|
+
- **Who runs the render loop** — browser-side animation with the CLI setting
|
|
217
|
+
targets (almost certainly right) or process-side frame pushing.
|
|
218
|
+
- **One face or many** per host.
|
|
219
|
+
- **Pre-rendered assets and their licence**, if any ship. A public repo with
|
|
220
|
+
vendored face art needs that answer before the first release.
|
|
221
|
+
|
|
222
|
+
The path for deciding them is the vendored workflow chain: `/scope` → `/think`
|
|
223
|
+
→ `/challenge` → `/spec-to-plan`, then `/assign-to-workforce` if the plan wants
|
|
224
|
+
parallel lanes.
|
|
225
|
+
|
|
226
|
+
## Identity
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
agents:
|
|
230
|
+
- suffix: face-cli
|
|
231
|
+
backend: colleague
|
|
232
|
+
model: sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`backend: colleague` fixes the resident prompt file to **`AGENTS.colleague.md`**
|
|
236
|
+
— the mesh runtime reads that file, while this `CLAUDE.md` is the Claude Code
|
|
237
|
+
guidance file. Both `steward doctor` and `face doctor` check the pair, and it
|
|
238
|
+
currently passes. (The scaffold seed this file replaced claimed
|
|
239
|
+
`backend: claude`; that was wrong — the checked-in value is `colleague`.) The
|
|
240
|
+
build brief asks that `culture.yaml` be reconciled against the backend actually
|
|
241
|
+
run; if this agent is promoted to `claude`, flipping the `backend` value is the
|
|
242
|
+
whole change — `doctor` already maps `claude` → `CLAUDE.md`, but
|
|
243
|
+
`tests/test_cli.py` asserts `backend: colleague` in two places and would need
|
|
244
|
+
updating with it.
|
|
245
|
+
|
|
246
|
+
## Conventions and workflow
|
|
247
|
+
|
|
248
|
+
- **Every PR bumps the version** — even docs/config/CI-only PRs. Use the
|
|
249
|
+
`version-bump` skill; the `version-check` CI job blocks merge otherwise, and
|
|
250
|
+
the bump script leaves the CHANGELOG sections blank, so write a real entry.
|
|
251
|
+
- **PRs go through the `cicd` skill** (`devex pr` + SonarCloud gating). Sign
|
|
252
|
+
online posts as `- face-cli (Claude)`; the `cicd` / `communicate` scripts
|
|
253
|
+
resolve that nick from `culture.yaml` automatically, so do not hand-sign in
|
|
254
|
+
bodies those scripts author.
|
|
255
|
+
- **Reach for `ask-colleague` reflexively.** Its value is a *second,
|
|
256
|
+
independent mind* (a different backend/model), not a stronger one. Before
|
|
257
|
+
presenting or opening a PR on a non-trivial committed diff, run `review`; for
|
|
258
|
+
a fresh read of an unfamiliar area, run `explore`. Both are read-only in a
|
|
259
|
+
throwaway worktree, so the reflex is always safe. `write --apply` / `--pr`
|
|
260
|
+
needs the user's go-ahead. Treat its output as an opinion to verify and own.
|
|
261
|
+
- **The vendored `.claude/skills/` are cited verbatim** — do not reformat or
|
|
262
|
+
edit their scripts. Re-sync from guildmaster (or the tracked direct-from-origin
|
|
263
|
+
exceptions) per `docs/skill-sources.md`. Prerequisites on PATH: `devex`
|
|
264
|
+
(>=0.21) and `agtag` (>=0.1); `colleague` is optional.
|
|
265
|
+
- **Deploy**: pushing to `main` publishes to PyPI via Trusted Publishing;
|
|
266
|
+
PRs do a TestPyPI dry-run. Both jobs only fire on changes to `pyproject.toml`
|
|
267
|
+
or `face_cli/**`. A PyPI / TestPyPI Trusted Publisher must be registered for
|
|
268
|
+
`face-cli` before the first real release — `guild create` configured the
|
|
269
|
+
GitHub side only.
|
|
270
|
+
|
|
271
|
+
### Worktrees
|
|
272
|
+
|
|
273
|
+
Every worktree you create by hand lives in one repo-named directory beside the
|
|
274
|
+
checkout, one subfolder per worktree:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
git worktree add ../.worktrees.face-cli/<name> -b <branch>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Never a shared `../worktrees/`: this workspace holds ~150 sibling projects, and
|
|
281
|
+
a generic shared folder accumulates orphaned trees from several repos at once
|
|
282
|
+
with nothing indicating ownership — a stale-tree sweep cannot tell a live lane
|
|
283
|
+
from junk. Scope the branch prefix to the work (`gaze/t2`, not `agent/t2`);
|
|
284
|
+
plain `agent/*` collides with leftovers from earlier fan-outs and `git worktree
|
|
285
|
+
add -b` fails on an existing branch. The vendored `assign-to-workforce` skill's
|
|
286
|
+
fan-out example uses *both* the shared path and `agent/<task-id>` branches —
|
|
287
|
+
it is cited verbatim and must not be edited, so override both when following
|
|
288
|
+
it. Remove with `git worktree remove <path>` (which deletes the directory);
|
|
289
|
+
`git worktree prune` only clears metadata for directories already gone. Never
|
|
290
|
+
`rm -rf` a worktree you did not create. Exception: `ask-colleague`'s read-only
|
|
291
|
+
verbs create their own detached worktree under `${TMPDIR:-/tmp}` and reap it on
|
|
292
|
+
an EXIT trap — outside this rule, not a violation of it.
|
|
293
|
+
|
|
294
|
+
### Memory discipline — recall before, remember after
|
|
295
|
+
|
|
296
|
+
This repo keeps its eidetic memory **in-repo and public**: a plain `/remember`
|
|
297
|
+
resolves to `<repo-root>/.eidetic/memory` — committed, shared with the team and
|
|
298
|
+
mesh peers, with the `claude` and `colleague` backends reading the same
|
|
299
|
+
`face-cli` scope. So memory travels with the repo rather than a private
|
|
300
|
+
home-dir store.
|
|
301
|
+
|
|
302
|
+
- **`/recall` before you start** a non-trivial task — prior decisions, gotchas,
|
|
303
|
+
"have we done this before?" — so you build on what is known.
|
|
304
|
+
- **`/remember` when something worth keeping surfaces**: a non-obvious decision
|
|
305
|
+
and its rationale, a constraint, a fix and *why*, a gotcha that cost time.
|
|
306
|
+
Capture it as it happens.
|
|
307
|
+
|
|
308
|
+
Pass `--visibility private` to keep a record out of the committed store (it
|
|
309
|
+
routes to `$HOME/.eidetic/memory`); `/recall` reads both and merges. Do not
|
|
310
|
+
store what the repo already records — code structure, git history, or anything
|
|
311
|
+
already in this file or `CHANGELOG.md`.
|
|
312
|
+
|
|
313
|
+
## Layout
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
face_cli/ agent-first CLI (cited from teken's python-cli reference)
|
|
317
|
+
cli/ parser, error/output contract, _commands/ (verbs)
|
|
318
|
+
explain/ markdown catalog for `explain`
|
|
319
|
+
tests/ pytest smoke + introspection tests
|
|
320
|
+
.claude/skills/ vendored guildmaster/devague/colleague skill kit (verbatim)
|
|
321
|
+
docs/skill-sources.md skill provenance ledger + re-sync procedure
|
|
322
|
+
culture.yaml mesh identity (suffix + backend)
|
|
323
|
+
AGENTS.colleague.md resident prompt for the colleague backend
|
|
324
|
+
.github/workflows/ tests + lint + version-check; PyPI Trusted Publishing
|
|
325
|
+
```
|
face_cli-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: face-cli
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: Scaffold for a browser-rendered face that reads three-dimensional on a flat 2D screen and looks in any direction — an agent-drivable gaze surface for robots, kiosks, and screens. The renderer is not implemented yet; the CLI ships introspection verbs only.
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentculture/face-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/agentculture/face-cli/issues
|
|
7
|
+
Author: AgentCulture
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# face-cli
|
|
19
|
+
|
|
20
|
+
A simulated face rendered in the browser that appears three-dimensional on a
|
|
21
|
+
flat 2D screen and can look in any direction — an agent-drivable gaze surface
|
|
22
|
+
for robots, kiosks, and screens.
|
|
23
|
+
|
|
24
|
+
> **Status: scaffold.** The renderer does not exist yet. What ships today is
|
|
25
|
+
> the agent-first CLI baseline, the mesh identity, and CI. The face itself is
|
|
26
|
+
> being designed in the open — see [the build brief][brief] and
|
|
27
|
+
> [`CLAUDE.md`](CLAUDE.md).
|
|
28
|
+
|
|
29
|
+
[brief]: https://github.com/agentculture/face-cli/issues/1
|
|
30
|
+
|
|
31
|
+
## The idea
|
|
32
|
+
|
|
33
|
+
Two halves, and both are the interesting part: the **illusion of depth** on a
|
|
34
|
+
flat surface, and a **gaze that can be pointed anywhere** and reads as pointed
|
|
35
|
+
there. Everything else is in service of those.
|
|
36
|
+
|
|
37
|
+
`face-cli` is the *expressive output* side of a face. Its sibling
|
|
38
|
+
[`face-recognition-cli`](https://github.com/agentculture/face-recognition-cli)
|
|
39
|
+
is the *perceptual input* side — it identifies real people from camera frames.
|
|
40
|
+
They share a subject and nothing else.
|
|
41
|
+
|
|
42
|
+
## Decided so far
|
|
43
|
+
|
|
44
|
+
- **The render target is the browser** — a local page driven over
|
|
45
|
+
canvas/WebGL/SVG. It needs no display server (so it runs on a headless robot
|
|
46
|
+
box), the screen showing the face need not be the machine running the
|
|
47
|
+
process, and a page can be screenshotted and diffed in CI. The *technique*
|
|
48
|
+
within the browser is still open.
|
|
49
|
+
- **The rendered face must survive face recognition.** Not photorealism — a
|
|
50
|
+
detector-passing bar: OpenCV **YuNet** should detect it above the standard
|
|
51
|
+
`0.6` score threshold, and **SFace**'s 128-dim embedding should stay stable
|
|
52
|
+
across the whole gaze range. That turns "it looks 3D" into an assertion CI
|
|
53
|
+
can actually make, and it is the seam along which this repo composes with
|
|
54
|
+
`face-recognition-cli`.
|
|
55
|
+
|
|
56
|
+
Open questions — the gaze primitive, the control channel between a one-shot CLI
|
|
57
|
+
call and a live page, whether expression is in scope, one face or many — are
|
|
58
|
+
tracked in [`CLAUDE.md`](CLAUDE.md) and [the brief][brief].
|
|
59
|
+
|
|
60
|
+
## Quickstart
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv sync
|
|
64
|
+
uv run pytest -n auto # run the test suite
|
|
65
|
+
uv run face whoami # identity from culture.yaml
|
|
66
|
+
uv run face learn # self-teaching prompt (add --json)
|
|
67
|
+
uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The console command is **`face`**; the import package is `face_cli` and the
|
|
71
|
+
PyPI distribution is `face-cli`. The mismatch is deliberate — a bare `face` is
|
|
72
|
+
already a published distribution on PyPI, so squatting that import would shadow
|
|
73
|
+
it.
|
|
74
|
+
|
|
75
|
+
## CLI
|
|
76
|
+
|
|
77
|
+
| Verb | What it does |
|
|
78
|
+
|------|--------------|
|
|
79
|
+
| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
|
|
80
|
+
| `learn` | Print a structured self-teaching prompt. |
|
|
81
|
+
| `explain <path>` | Markdown docs for any noun/verb path. |
|
|
82
|
+
| `overview` | Read-only descriptive snapshot of the agent. |
|
|
83
|
+
| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
|
|
84
|
+
| `cli overview` | Describe the CLI surface itself. |
|
|
85
|
+
|
|
86
|
+
Every command supports `--json`. Results go to stdout, errors/diagnostics to
|
|
87
|
+
stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
|
|
88
|
+
error, `3+` reserved.
|
|
89
|
+
|
|
90
|
+
No gaze or render verbs exist yet — the list above is the scaffold's
|
|
91
|
+
introspection surface.
|
|
92
|
+
|
|
93
|
+
## What's in the box
|
|
94
|
+
|
|
95
|
+
- **An agent-first CLI** cited from [teken](https://github.com/agentculture/teken)
|
|
96
|
+
(`afi-cli`) — the runtime package has no third-party dependencies.
|
|
97
|
+
- **A mesh identity** — `culture.yaml` (`suffix` + `backend`) and the matching
|
|
98
|
+
resident prompt file (`AGENTS.colleague.md`, since this agent runs
|
|
99
|
+
`backend: colleague`).
|
|
100
|
+
- **The canonical guildmaster skill kit** under `.claude/skills/`, vendored
|
|
101
|
+
cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md).
|
|
102
|
+
- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, and
|
|
103
|
+
PyPI Trusted Publishing wired into GitHub Actions.
|
|
104
|
+
|
|
105
|
+
## Contributing
|
|
106
|
+
|
|
107
|
+
Every PR bumps the version (`version-check` CI blocks merge otherwise) and adds
|
|
108
|
+
a real CHANGELOG entry. See [`CLAUDE.md`](CLAUDE.md) for the full conventions —
|
|
109
|
+
the `cicd` PR lane, worktree layout, and deploy setup.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
Apache 2.0 — see [`LICENSE`](LICENSE).
|
face_cli-0.7.1/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# face-cli
|
|
2
|
+
|
|
3
|
+
A simulated face rendered in the browser that appears three-dimensional on a
|
|
4
|
+
flat 2D screen and can look in any direction — an agent-drivable gaze surface
|
|
5
|
+
for robots, kiosks, and screens.
|
|
6
|
+
|
|
7
|
+
> **Status: scaffold.** The renderer does not exist yet. What ships today is
|
|
8
|
+
> the agent-first CLI baseline, the mesh identity, and CI. The face itself is
|
|
9
|
+
> being designed in the open — see [the build brief][brief] and
|
|
10
|
+
> [`CLAUDE.md`](CLAUDE.md).
|
|
11
|
+
|
|
12
|
+
[brief]: https://github.com/agentculture/face-cli/issues/1
|
|
13
|
+
|
|
14
|
+
## The idea
|
|
15
|
+
|
|
16
|
+
Two halves, and both are the interesting part: the **illusion of depth** on a
|
|
17
|
+
flat surface, and a **gaze that can be pointed anywhere** and reads as pointed
|
|
18
|
+
there. Everything else is in service of those.
|
|
19
|
+
|
|
20
|
+
`face-cli` is the *expressive output* side of a face. Its sibling
|
|
21
|
+
[`face-recognition-cli`](https://github.com/agentculture/face-recognition-cli)
|
|
22
|
+
is the *perceptual input* side — it identifies real people from camera frames.
|
|
23
|
+
They share a subject and nothing else.
|
|
24
|
+
|
|
25
|
+
## Decided so far
|
|
26
|
+
|
|
27
|
+
- **The render target is the browser** — a local page driven over
|
|
28
|
+
canvas/WebGL/SVG. It needs no display server (so it runs on a headless robot
|
|
29
|
+
box), the screen showing the face need not be the machine running the
|
|
30
|
+
process, and a page can be screenshotted and diffed in CI. The *technique*
|
|
31
|
+
within the browser is still open.
|
|
32
|
+
- **The rendered face must survive face recognition.** Not photorealism — a
|
|
33
|
+
detector-passing bar: OpenCV **YuNet** should detect it above the standard
|
|
34
|
+
`0.6` score threshold, and **SFace**'s 128-dim embedding should stay stable
|
|
35
|
+
across the whole gaze range. That turns "it looks 3D" into an assertion CI
|
|
36
|
+
can actually make, and it is the seam along which this repo composes with
|
|
37
|
+
`face-recognition-cli`.
|
|
38
|
+
|
|
39
|
+
Open questions — the gaze primitive, the control channel between a one-shot CLI
|
|
40
|
+
call and a live page, whether expression is in scope, one face or many — are
|
|
41
|
+
tracked in [`CLAUDE.md`](CLAUDE.md) and [the brief][brief].
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv sync
|
|
47
|
+
uv run pytest -n auto # run the test suite
|
|
48
|
+
uv run face whoami # identity from culture.yaml
|
|
49
|
+
uv run face learn # self-teaching prompt (add --json)
|
|
50
|
+
uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The console command is **`face`**; the import package is `face_cli` and the
|
|
54
|
+
PyPI distribution is `face-cli`. The mismatch is deliberate — a bare `face` is
|
|
55
|
+
already a published distribution on PyPI, so squatting that import would shadow
|
|
56
|
+
it.
|
|
57
|
+
|
|
58
|
+
## CLI
|
|
59
|
+
|
|
60
|
+
| Verb | What it does |
|
|
61
|
+
|------|--------------|
|
|
62
|
+
| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
|
|
63
|
+
| `learn` | Print a structured self-teaching prompt. |
|
|
64
|
+
| `explain <path>` | Markdown docs for any noun/verb path. |
|
|
65
|
+
| `overview` | Read-only descriptive snapshot of the agent. |
|
|
66
|
+
| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
|
|
67
|
+
| `cli overview` | Describe the CLI surface itself. |
|
|
68
|
+
|
|
69
|
+
Every command supports `--json`. Results go to stdout, errors/diagnostics to
|
|
70
|
+
stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
|
|
71
|
+
error, `3+` reserved.
|
|
72
|
+
|
|
73
|
+
No gaze or render verbs exist yet — the list above is the scaffold's
|
|
74
|
+
introspection surface.
|
|
75
|
+
|
|
76
|
+
## What's in the box
|
|
77
|
+
|
|
78
|
+
- **An agent-first CLI** cited from [teken](https://github.com/agentculture/teken)
|
|
79
|
+
(`afi-cli`) — the runtime package has no third-party dependencies.
|
|
80
|
+
- **A mesh identity** — `culture.yaml` (`suffix` + `backend`) and the matching
|
|
81
|
+
resident prompt file (`AGENTS.colleague.md`, since this agent runs
|
|
82
|
+
`backend: colleague`).
|
|
83
|
+
- **The canonical guildmaster skill kit** under `.claude/skills/`, vendored
|
|
84
|
+
cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md).
|
|
85
|
+
- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, and
|
|
86
|
+
PyPI Trusted Publishing wired into GitHub Actions.
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Every PR bumps the version (`version-check` CI blocks merge otherwise) and adds
|
|
91
|
+
a real CHANGELOG entry. See [`CLAUDE.md`](CLAUDE.md) for the full conventions —
|
|
92
|
+
the `cicd` PR lane, worktree layout, and deploy setup.
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
Apache 2.0 — see [`LICENSE`](LICENSE).
|
|
@@ -26,7 +26,7 @@ import sys
|
|
|
26
26
|
|
|
27
27
|
from face_cli import __version__
|
|
28
28
|
from face_cli.cli._errors import EXIT_USER_ERROR, CliError
|
|
29
|
-
from face_cli.cli._output import emit_error
|
|
29
|
+
from face_cli.cli._output import emit_error, emit_result
|
|
30
30
|
|
|
31
31
|
_ISSUES_URL = "https://github.com/agentculture/face-cli/issues"
|
|
32
32
|
|
|
@@ -71,13 +71,23 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
71
71
|
|
|
72
72
|
parser = _CliArgumentParser(
|
|
73
73
|
prog="face-cli",
|
|
74
|
-
description="face-cli — a
|
|
74
|
+
description="face-cli — a browser-rendered face that reads 3D on a 2D screen "
|
|
75
|
+
"and looks in any direction. Scaffold: no renderer or gaze verbs yet; the "
|
|
76
|
+
"verbs below are the introspection surface.",
|
|
75
77
|
)
|
|
76
78
|
parser.add_argument(
|
|
77
79
|
"--version",
|
|
78
80
|
action="version",
|
|
79
81
|
version=f"%(prog)s {__version__}",
|
|
80
82
|
)
|
|
83
|
+
# Accepted before the verb too (`face --json whoami`), not just after it.
|
|
84
|
+
# argparse gives each subparser its own `json` default, so a leading flag is
|
|
85
|
+
# clobbered on the way down; main() re-asserts it from the raw-argv scan.
|
|
86
|
+
parser.add_argument(
|
|
87
|
+
"--json",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help="Emit structured JSON. Accepted before or after the verb.",
|
|
90
|
+
)
|
|
81
91
|
# parser_class propagates to every subparser so their .error() routes
|
|
82
92
|
# through _CliArgumentParser too.
|
|
83
93
|
sub = parser.add_subparsers(dest="command", parser_class=_CliArgumentParser)
|
|
@@ -125,7 +135,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
125
135
|
parser = _build_parser()
|
|
126
136
|
args = parser.parse_args(argv)
|
|
127
137
|
|
|
138
|
+
# Position must not change meaning: each subparser re-declares `--json` with
|
|
139
|
+
# its own `False` default, which overwrites a leading `--json` during
|
|
140
|
+
# sub-parsing. Re-assert from the raw-argv scan so `face --json whoami` and
|
|
141
|
+
# `face whoami --json` behave identically.
|
|
142
|
+
if _CliArgumentParser._json_hint:
|
|
143
|
+
args.json = True
|
|
144
|
+
|
|
128
145
|
if args.command is None:
|
|
146
|
+
# No verb: the JSON analogue of printing help is the machine-readable
|
|
147
|
+
# command map, which `learn` already owns.
|
|
148
|
+
if getattr(args, "json", False):
|
|
149
|
+
from face_cli.cli._commands.learn import as_json_payload
|
|
150
|
+
|
|
151
|
+
emit_result(as_json_payload(), json_mode=True)
|
|
152
|
+
return 0
|
|
129
153
|
parser.print_help()
|
|
130
154
|
return 0
|
|
131
155
|
|