feat: initial Clare scaffold — project manager for the Claude agent fleet
Push A (single-machine):
- HLC + event-sourced SQLite (events table is source of truth, projections rebuildable)
- Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update)
- rclaude subprocess wrapper (local_sessions via _claude-projects --sessions)
- Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync
- FastAPI + Jinja2 + HTMX dashboard
- 26 unit tests passing
Push B (HTTP API + sync substrate):
- /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync)
- CLI refactored as thin httpx client over the API — single business-logic codepath
- web/service.py: every business op defined once; HTML routes + API routes both call into it
- sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup
- 32 tests passing including two-Clare convergence test
Push C (cross-host deployment):
- apricot install via uv (Python 3.12.12)
- systemd --user unit for clare-serve on apricot
- Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg
- .local → .lan rename for forge URLs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:20:23 -07:00
|
|
|
"""CLI smoke tests.
|
|
|
|
|
|
|
|
|
|
Most CLI behavior is now indirectly covered by test_api.py (the CLI is a
|
|
|
|
|
thin httpx wrapper over the JSON API). These tests verify the local-only
|
|
|
|
|
verbs (`init`) and the "server unreachable" error path.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
|
|
2026-05-20 19:54:05 -07:00
|
|
|
from claire.cli import app
|
feat: initial Clare scaffold — project manager for the Claude agent fleet
Push A (single-machine):
- HLC + event-sourced SQLite (events table is source of truth, projections rebuildable)
- Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update)
- rclaude subprocess wrapper (local_sessions via _claude-projects --sessions)
- Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync
- FastAPI + Jinja2 + HTMX dashboard
- 26 unit tests passing
Push B (HTTP API + sync substrate):
- /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync)
- CLI refactored as thin httpx client over the API — single business-logic codepath
- web/service.py: every business op defined once; HTML routes + API routes both call into it
- sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup
- 32 tests passing including two-Clare convergence test
Push C (cross-host deployment):
- apricot install via uv (Python 3.12.12)
- systemd --user unit for clare-serve on apricot
- Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg
- .local → .lan rename for forge URLs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:20:23 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_init_creates_config_and_db(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
|
|
|
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config"))
|
|
|
|
|
runner = CliRunner()
|
|
|
|
|
result = runner.invoke(app, ["init"])
|
|
|
|
|
assert result.exit_code == 0, result.output
|
|
|
|
|
assert "machine_id" in result.output
|
2026-05-20 19:54:05 -07:00
|
|
|
assert (tmp_path / "config" / "claire" / "claire.toml").exists()
|
|
|
|
|
assert (tmp_path / "data" / "claire" / "claire.db").exists()
|
feat: initial Clare scaffold — project manager for the Claude agent fleet
Push A (single-machine):
- HLC + event-sourced SQLite (events table is source of truth, projections rebuildable)
- Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update)
- rclaude subprocess wrapper (local_sessions via _claude-projects --sessions)
- Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync
- FastAPI + Jinja2 + HTMX dashboard
- 26 unit tests passing
Push B (HTTP API + sync substrate):
- /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync)
- CLI refactored as thin httpx client over the API — single business-logic codepath
- web/service.py: every business op defined once; HTML routes + API routes both call into it
- sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup
- 32 tests passing including two-Clare convergence test
Push C (cross-host deployment):
- apricot install via uv (Python 3.12.12)
- systemd --user unit for clare-serve on apricot
- Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg
- .local → .lan rename for forge URLs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:20:23 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cli_emits_helpful_error_when_server_down(
|
|
|
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
2026-05-20 19:54:05 -07:00
|
|
|
"""`claire project list` should exit 3 with a clear message when the server
|
feat: initial Clare scaffold — project manager for the Claude agent fleet
Push A (single-machine):
- HLC + event-sourced SQLite (events table is source of truth, projections rebuildable)
- Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update)
- rclaude subprocess wrapper (local_sessions via _claude-projects --sessions)
- Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync
- FastAPI + Jinja2 + HTMX dashboard
- 26 unit tests passing
Push B (HTTP API + sync substrate):
- /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync)
- CLI refactored as thin httpx client over the API — single business-logic codepath
- web/service.py: every business op defined once; HTML routes + API routes both call into it
- sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup
- 32 tests passing including two-Clare convergence test
Push C (cross-host deployment):
- apricot install via uv (Python 3.12.12)
- systemd --user unit for clare-serve on apricot
- Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg
- .local → .lan rename for forge URLs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:20:23 -07:00
|
|
|
isn't running. We point at a definitely-dead port to force ConnectError."""
|
|
|
|
|
monkeypatch.setenv("XDG_DATA_HOME", str(tmp_path / "data"))
|
|
|
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config"))
|
|
|
|
|
|
|
|
|
|
# Init creates the config so we can mutate it.
|
|
|
|
|
runner = CliRunner()
|
|
|
|
|
runner.invoke(app, ["init"])
|
|
|
|
|
|
|
|
|
|
# Point the CLI at a dead port.
|
2026-05-20 19:54:05 -07:00
|
|
|
cfg_path = tmp_path / "config" / "claire" / "claire.toml"
|
feat: initial Clare scaffold — project manager for the Claude agent fleet
Push A (single-machine):
- HLC + event-sourced SQLite (events table is source of truth, projections rebuildable)
- Pydantic v2 domain models (Project, Task, Assignment, Session, Group, Update)
- rclaude subprocess wrapper (local_sessions via _claude-projects --sessions)
- Typer CLI: init, project, task, assign, pull, status, broadcast, serve, sync
- FastAPI + Jinja2 + HTMX dashboard
- 26 unit tests passing
Push B (HTTP API + sync substrate):
- /api/v1/* JSON routes (projects, tasks, assignments, sessions, status, broadcast, sync)
- CLI refactored as thin httpx client over the API — single business-logic codepath
- web/service.py: every business op defined once; HTML routes + API routes both call into it
- sync.py: peer-to-peer sync via /api/v1/sync/events with HLC + uuid-based dedup
- 32 tests passing including two-Clare convergence test
Push C (cross-host deployment):
- apricot install via uv (Python 3.12.12)
- systemd --user unit for clare-serve on apricot
- Cross-host sync demoed plum (10.9.0.3) ↔ apricot (10.9.0.2) over wg
- .local → .lan rename for forge URLs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 02:20:23 -07:00
|
|
|
content = cfg_path.read_text()
|
|
|
|
|
content = content.replace("port = 8765", "port = 1")
|
|
|
|
|
cfg_path.write_text(content)
|
|
|
|
|
|
|
|
|
|
result = runner.invoke(app, ["project", "list"])
|
|
|
|
|
assert result.exit_code == 3
|
2026-05-20 19:54:05 -07:00
|
|
|
assert "could not reach claire server" in result.output
|
|
|
|
|
assert "claire serve" in result.output
|