reachy-cli 0.2.0__tar.gz → 0.3.0__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.
Files changed (78) hide show
  1. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/CHANGELOG.md +14 -0
  2. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/CLAUDE.md +22 -4
  3. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/PKG-INFO +48 -8
  4. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/README.md +45 -7
  5. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/pyproject.toml +13 -5
  6. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/__init__.py +3 -1
  7. reachy_cli-0.3.0/reachy/cli/_commands/daemon.py +165 -0
  8. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/learn.py +4 -0
  9. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/overview.py +1 -0
  10. reachy_cli-0.3.0/reachy/daemon.py +362 -0
  11. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/explain/catalog.py +58 -3
  12. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/robot/http_transport.py +2 -1
  13. reachy_cli-0.3.0/tests/test_daemon.py +420 -0
  14. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/tests/test_robot.py +1 -1
  15. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/uv.lock +9 -3
  16. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/agent-config/SKILL.md +0 -0
  17. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
  18. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
  19. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
  20. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
  21. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/SKILL.md +0 -0
  22. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
  23. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
  24. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
  25. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
  26. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
  27. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/SKILL.md +0 -0
  28. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
  29. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
  30. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
  31. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
  32. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
  33. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
  34. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
  35. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
  36. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
  37. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
  38. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/run-tests/SKILL.md +0 -0
  39. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
  40. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
  41. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
  42. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
  43. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
  44. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/think/SKILL.md +0 -0
  45. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/think/scripts/think.sh +0 -0
  46. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/version-bump/SKILL.md +0 -0
  47. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
  48. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.claude/skills.local.yaml.example +0 -0
  49. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.flake8 +0 -0
  50. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.github/workflows/publish.yml +0 -0
  51. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.github/workflows/tests.yml +0 -0
  52. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.gitignore +0 -0
  53. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/.markdownlint-cli2.yaml +0 -0
  54. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/LICENSE +0 -0
  55. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/culture.yaml +0 -0
  56. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/docs/adr-0001-sdk-transport-extra.md +0 -0
  57. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/docs/skill-sources.md +0 -0
  58. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/__init__.py +0 -0
  59. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/__main__.py +0 -0
  60. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/__init__.py +0 -0
  61. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/_robot.py +0 -0
  62. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/app.py +0 -0
  63. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/cli.py +0 -0
  64. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/device.py +0 -0
  65. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/doctor.py +0 -0
  66. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/explain.py +0 -0
  67. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/move.py +0 -0
  68. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_commands/whoami.py +0 -0
  69. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_errors.py +0 -0
  70. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/cli/_output.py +0 -0
  71. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/explain/__init__.py +0 -0
  72. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/robot/__init__.py +0 -0
  73. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/robot/sdk_transport.py +0 -0
  74. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/reachy/robot/transport.py +0 -0
  75. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/sonar-project.properties +0 -0
  76. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/tests/__init__.py +0 -0
  77. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/tests/test_cli.py +0 -0
  78. {reachy_cli-0.2.0 → reachy_cli-0.3.0}/tests/test_cli_introspection.py +0 -0
@@ -5,6 +5,20 @@ 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.3.0] - 2026-05-30
9
+
10
+ ### Added
11
+
12
+ - `daemon` noun group (`start`/`stop`/`status`/`overview`): bring the local `reachy-mini-daemon` process up and down — background spawn + PID/log under `$XDG_STATE_HOME/reachy`, health-poll on `GET /api/daemon/status`, idempotent start, SIGTERM-then-SIGKILL stop.
13
+ - `reachy/daemon.py` — stdlib-only daemon process-lifecycle module (no new runtime dependency).
14
+ - `[daemon]` optional-dependencies extra (`reachy-mini>=1.0`) — the recommended default install, providing the `reachy-mini-daemon` binary.
15
+
16
+ ### Changed
17
+
18
+ - Inverted the install model: `pip install 'reachy-cli[daemon]'` is now the default (bundles the daemon); the bare `pip install reachy-cli` is the HTTP-only *remote* profile. Base stays zero-runtime-deps.
19
+ - The `http` transport's daemon-unreachable hint now points at `reachy daemon start` and the `[daemon]` install.
20
+ - README + CLAUDE.md document the daemon noun, the install profiles, and the daemon-up wake-up flow.
21
+
8
22
  ## [0.2.0] - 2026-05-30
9
23
 
10
24
  ### Added
@@ -40,7 +40,9 @@ test assertions in one pass.
40
40
 
41
41
  ```bash
42
42
  uv sync # create .venv, install (dev deps incl. teken)
43
+ uv sync --extra daemon # + reachy-mini (the reachy-mini-daemon binary)
43
44
  uv run reachy whoami # run the CLI (NOT `reachy-mini-cli`)
45
+ uv run reachy daemon start # bring the local daemon up (needs [daemon] extra)
44
46
  uv run pytest -n auto # full suite (parallel)
45
47
  uv run pytest tests/test_cli.py::test_whoami_text # a single test
46
48
  uv run pytest --cov=reachy --cov-report=term # with coverage (CI gate: fail_under=60)
@@ -93,13 +95,29 @@ you touch the CLI.
93
95
  line scanner (no YAML library) and walks up from `__file__` to find it.
94
96
  `doctor` re-implements the steward invariants (prompt-file-present,
95
97
  backend-consistency `claude`→`CLAUDE.md`, skills-present).
98
+ - **`daemon` noun & process module:** `device`/`app`/`move` are *clients* of a
99
+ running daemon; `reachy/cli/_commands/daemon.py` + `reachy/daemon.py` are the
100
+ other half — they start/stop/status the local `reachy-mini-daemon` OS process
101
+ (background spawn + PID/log under `$REACHY_STATE_DIR` / `$XDG_STATE_HOME/reachy`,
102
+ health-poll via `GET /api/daemon/status`). Pure stdlib (`subprocess`/`signal`/
103
+ `urllib`); the daemon *binary* comes from the `[daemon]` extra. Its `overview`
104
+ is hand-built (no `--transport sdk` line) — `daemon` does NOT use a transport,
105
+ so it does not call `_robot.noun_overview`/`get_transport`. A missing binary
106
+ raises a clean exit-2 `CliError` pointing at the `[daemon]` install.
96
107
 
97
108
  ## Hard constraints
98
109
 
99
- - **Zero runtime dependencies.** `pyproject.toml` has `dependencies = []` on
100
- purpose; `teken` is dev-only. This is why `whoami` hand-rolls YAML parsing.
101
- Do not add a third-party runtime import without an explicit decision to change
102
- this it would break the "self-contained runtime" property the README sells.
110
+ - **Zero *base* runtime dependencies.** `pyproject.toml` keeps base
111
+ `dependencies = []` on purpose; `teken` is dev-only. This is why `whoami`
112
+ hand-rolls YAML parsing and `reachy/daemon.py` manages the daemon process with
113
+ stdlib `subprocess`/`urllib` only. The **recommended default install is `pip
114
+ install 'reachy-cli[daemon]'`** (pulls `reachy-mini` for the
115
+ `reachy-mini-daemon` binary); the bare `pip install reachy-cli` is the
116
+ HTTP-only *remote* profile. Keep the *base* dep-free — anything that needs
117
+ `reachy-mini` (the `sdk` transport, the daemon binary) goes behind the
118
+ `[daemon]`/`[sdk]` extras, never into base `dependencies`. Adding a base
119
+ runtime dep needs an explicit decision; it would break the dependency-free
120
+ remote profile the README sells.
103
121
  - **Python ≥ 3.12** (uses `X | None`, `tomllib`, etc.).
104
122
  - **Every PR bumps the version**, even docs/config/CI-only changes — the
105
123
  `version-check` CI job blocks the merge otherwise (it compares
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reachy-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Agent and CLI for operating the Reachy Mini expressive robot — device setup, app management, and runtime ops.
5
5
  Project-URL: Homepage, https://github.com/agentculture/reachy-mini-cli
6
6
  Project-URL: Issues, https://github.com/agentculture/reachy-mini-cli/issues
@@ -13,6 +13,8 @@ Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Topic :: Software Development
15
15
  Requires-Python: >=3.12
16
+ Provides-Extra: daemon
17
+ Requires-Dist: reachy-mini>=1.0; extra == 'daemon'
16
18
  Provides-Extra: sdk
17
19
  Requires-Dist: reachy-mini>=1.0; extra == 'sdk'
18
20
  Description-Content-Type: text/markdown
@@ -35,10 +37,11 @@ Agent and CLI for operating the Reachy Mini expressive robot — device setup, a
35
37
  ## Quickstart
36
38
 
37
39
  ```bash
38
- uv sync
40
+ uv sync --extra daemon # default: + the local reachy-mini-daemon
41
+ # uv sync # remote profile: HTTP-only, no daemon deps
39
42
  uv run pytest -n auto # run the test suite
40
- uv run reachy-mini-cli whoami # identity from culture.yaml
41
- uv run reachy-mini-cli learn # self-teaching prompt (add --json)
43
+ uv run reachy whoami # identity from culture.yaml
44
+ uv run reachy learn # self-teaching prompt (add --json)
42
45
  uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
43
46
  ```
44
47
 
@@ -59,8 +62,44 @@ error, `3+` reserved.
59
62
 
60
63
  ## Robot operations
61
64
 
62
- The `device`, `app`, and `move` noun groups operate the Reachy Mini. They talk
63
- to the robot through a selectable **transport flavor**:
65
+ The `daemon`, `device`, `app`, and `move` noun groups operate the Reachy Mini.
66
+
67
+ ### Install profiles
68
+
69
+ The Reachy daemon (`reachy-mini-daemon`) and the in-process SDK ship in
70
+ `reachy-mini`. Choose your install by where the daemon runs:
71
+
72
+ - **Default — with the daemon:** `pip install 'reachy-cli[daemon]'`. Bundles
73
+ `reachy-mini`, so `reachy daemon start` can bring the daemon up locally. This
74
+ is the profile for a machine with a robot attached.
75
+ - **Remote — without the daemon:** `pip install reachy-cli` (bare). The base
76
+ install keeps **zero runtime dependencies** (the `http` transport and the
77
+ `daemon status`/`stop` verbs use only the stdlib). Use it on a control box that
78
+ only talks to a daemon running elsewhere via `--base-url` / `REACHY_BASE_URL`.
79
+ `daemon start` here exits `2` with a hint to install the `[daemon]` extra.
80
+
81
+ `[sdk]` (also `reachy-mini`) adds the in-process `--transport sdk` client.
82
+
83
+ ### Bring the daemon up
84
+
85
+ `device`/`app`/`move` are clients of a running daemon; `daemon` is the other
86
+ half — it manages the local `reachy-mini-daemon` process.
87
+
88
+ | Verb | What it does |
89
+ |------|--------------|
90
+ | `daemon start` | Spawn `reachy-mini-daemon` in the background, then poll its health route until ready. Idempotent. |
91
+ | `daemon stop` | Stop the daemon this CLI started (SIGTERM, then SIGKILL). |
92
+ | `daemon status` | Reconcile the tracked process (running/stopped/stale) with the HTTP health check. |
93
+
94
+ `reachy-mini-daemon` defaults to `--wake-up-on-start`, so `daemon start` already
95
+ wakes the robot. Forward daemon args after `--`, e.g.
96
+ `reachy daemon start -- --sim --no-wake-up-on-start`. State (PID + log) lives
97
+ under `$XDG_STATE_HOME/reachy` (`~/.local/state/reachy`).
98
+
99
+ ### Transports
100
+
101
+ The `device`, `app`, and `move` verbs talk to a running daemon through a
102
+ selectable **transport flavor**:
64
103
 
65
104
  - **`http`** (default) — the Reachy daemon's REST API. Uses only the Python
66
105
  standard library, so the default install keeps **zero runtime dependencies**.
@@ -89,11 +128,12 @@ with a clean `error:`/`hint:` pair — never a traceback.
89
128
  Each noun also exposes `overview` (e.g. `reachy move overview`).
90
129
 
91
130
  ```bash
92
- # Start the daemon (from the reachy_mini SDK), then:
93
- uv run reachy device status
131
+ uv run reachy daemon start # bring the local daemon up (and wake the robot)
132
+ uv run reachy device status # now answers instead of exit-2
94
133
  uv run reachy app list --json
95
134
  uv run reachy move goto --z 10 --pitch -5 --duration 2
96
135
  uv run reachy move wake
136
+ uv run reachy daemon stop # put it back down when you're done
97
137
  ```
98
138
 
99
139
  ## Make it your own
@@ -16,10 +16,11 @@ Agent and CLI for operating the Reachy Mini expressive robot — device setup, a
16
16
  ## Quickstart
17
17
 
18
18
  ```bash
19
- uv sync
19
+ uv sync --extra daemon # default: + the local reachy-mini-daemon
20
+ # uv sync # remote profile: HTTP-only, no daemon deps
20
21
  uv run pytest -n auto # run the test suite
21
- uv run reachy-mini-cli whoami # identity from culture.yaml
22
- uv run reachy-mini-cli learn # self-teaching prompt (add --json)
22
+ uv run reachy whoami # identity from culture.yaml
23
+ uv run reachy learn # self-teaching prompt (add --json)
23
24
  uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
24
25
  ```
25
26
 
@@ -40,8 +41,44 @@ error, `3+` reserved.
40
41
 
41
42
  ## Robot operations
42
43
 
43
- The `device`, `app`, and `move` noun groups operate the Reachy Mini. They talk
44
- to the robot through a selectable **transport flavor**:
44
+ The `daemon`, `device`, `app`, and `move` noun groups operate the Reachy Mini.
45
+
46
+ ### Install profiles
47
+
48
+ The Reachy daemon (`reachy-mini-daemon`) and the in-process SDK ship in
49
+ `reachy-mini`. Choose your install by where the daemon runs:
50
+
51
+ - **Default — with the daemon:** `pip install 'reachy-cli[daemon]'`. Bundles
52
+ `reachy-mini`, so `reachy daemon start` can bring the daemon up locally. This
53
+ is the profile for a machine with a robot attached.
54
+ - **Remote — without the daemon:** `pip install reachy-cli` (bare). The base
55
+ install keeps **zero runtime dependencies** (the `http` transport and the
56
+ `daemon status`/`stop` verbs use only the stdlib). Use it on a control box that
57
+ only talks to a daemon running elsewhere via `--base-url` / `REACHY_BASE_URL`.
58
+ `daemon start` here exits `2` with a hint to install the `[daemon]` extra.
59
+
60
+ `[sdk]` (also `reachy-mini`) adds the in-process `--transport sdk` client.
61
+
62
+ ### Bring the daemon up
63
+
64
+ `device`/`app`/`move` are clients of a running daemon; `daemon` is the other
65
+ half — it manages the local `reachy-mini-daemon` process.
66
+
67
+ | Verb | What it does |
68
+ |------|--------------|
69
+ | `daemon start` | Spawn `reachy-mini-daemon` in the background, then poll its health route until ready. Idempotent. |
70
+ | `daemon stop` | Stop the daemon this CLI started (SIGTERM, then SIGKILL). |
71
+ | `daemon status` | Reconcile the tracked process (running/stopped/stale) with the HTTP health check. |
72
+
73
+ `reachy-mini-daemon` defaults to `--wake-up-on-start`, so `daemon start` already
74
+ wakes the robot. Forward daemon args after `--`, e.g.
75
+ `reachy daemon start -- --sim --no-wake-up-on-start`. State (PID + log) lives
76
+ under `$XDG_STATE_HOME/reachy` (`~/.local/state/reachy`).
77
+
78
+ ### Transports
79
+
80
+ The `device`, `app`, and `move` verbs talk to a running daemon through a
81
+ selectable **transport flavor**:
45
82
 
46
83
  - **`http`** (default) — the Reachy daemon's REST API. Uses only the Python
47
84
  standard library, so the default install keeps **zero runtime dependencies**.
@@ -70,11 +107,12 @@ with a clean `error:`/`hint:` pair — never a traceback.
70
107
  Each noun also exposes `overview` (e.g. `reachy move overview`).
71
108
 
72
109
  ```bash
73
- # Start the daemon (from the reachy_mini SDK), then:
74
- uv run reachy device status
110
+ uv run reachy daemon start # bring the local daemon up (and wake the robot)
111
+ uv run reachy device status # now answers instead of exit-2
75
112
  uv run reachy app list --json
76
113
  uv run reachy move goto --z 10 --pitch -5 --duration 2
77
114
  uv run reachy move wake
115
+ uv run reachy daemon stop # put it back down when you're done
78
116
  ```
79
117
 
80
118
  ## Make it your own
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "reachy-cli"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "Agent and CLI for operating the Reachy Mini expressive robot — device setup, app management, and runtime ops."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -15,12 +15,20 @@ classifiers = [
15
15
  ]
16
16
  dependencies = []
17
17
 
18
- # Optional "sdk" transport flavor. The default install stays dependency-free
19
- # (the http transport uses only the stdlib); installing the extra enables
20
- # `--transport sdk`, which drives the robot through the in-process client.
21
- # Sanctioned exception to the zero-runtime-dependency rule — see
18
+ # Optional extras. The base install stays dependency-free (the http transport and
19
+ # the `daemon` process verbs use only the stdlib), so the bare `pip install
20
+ # reachy-cli` is the lightweight HTTP-only *remote* profile for a control box
21
+ # that only talks to a daemon running elsewhere via --base-url / REACHY_BASE_URL.
22
+ #
23
+ # The recommended *default* is `pip install 'reachy-cli[daemon]'`: it pulls in
24
+ # `reachy-mini`, which provides the `reachy-mini-daemon` binary that `reachy
25
+ # daemon start` launches. `[sdk]` enables the in-process `--transport sdk` client.
26
+ # Both resolve to the same `reachy-mini` wheel today (it ships both the daemon
27
+ # binary and the SDK client); they stay separately named for intent clarity.
28
+ # Sanctioned exceptions to the zero-runtime-dependency rule — see
22
29
  # docs/adr-0001-sdk-transport-extra.md.
23
30
  [project.optional-dependencies]
31
+ daemon = ["reachy-mini>=1.0"]
24
32
  sdk = ["reachy-mini>=1.0"]
25
33
 
26
34
  [project.urls]
@@ -64,6 +64,7 @@ def _argv_has_json(argv: list[str] | None) -> bool:
64
64
  def _build_parser() -> argparse.ArgumentParser:
65
65
  from reachy.cli._commands import app as _app_group
66
66
  from reachy.cli._commands import cli as _cli_group
67
+ from reachy.cli._commands import daemon as _daemon_group
67
68
  from reachy.cli._commands import device as _device_group
68
69
  from reachy.cli._commands import doctor as _doctor_cmd
69
70
  from reachy.cli._commands import explain as _explain_cmd
@@ -91,7 +92,8 @@ def _build_parser() -> argparse.ArgumentParser:
91
92
  _overview_cmd.register(sub)
92
93
  _doctor_cmd.register(sub)
93
94
  _cli_group.register(sub)
94
- # Robot noun groups (device setup, app management, runtime ops).
95
+ # Robot noun groups (daemon lifecycle, device setup, app mgmt, runtime ops).
96
+ _daemon_group.register(sub)
95
97
  _device_group.register(sub)
96
98
  _app_group.register(sub)
97
99
  _move_group.register(sub)
@@ -0,0 +1,165 @@
1
+ """``reachy-mini-cli daemon`` — local daemon process lifecycle.
2
+
3
+ The robot verbs (``device`` / ``app`` / ``move``) talk *to* a running daemon;
4
+ this noun is the other half — it brings the local ``reachy-mini-daemon`` process
5
+ up and down. ``start`` spawns it in the background and waits for its health
6
+ route, ``stop`` terminates it, ``status`` reconciles the tracked process with the
7
+ HTTP health check. The daemon binary ships in the ``[daemon]`` extra; a missing
8
+ binary yields a clean exit-2 hint pointing at the install.
9
+
10
+ Note: ``reachy-mini-daemon`` defaults to ``--wake-up-on-start``, so ``daemon
11
+ start`` already wakes the robot. Forward ``-- --no-wake-up-on-start`` to skip it.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import os
18
+
19
+ from reachy import daemon as _daemon
20
+ from reachy.cli._commands._robot import emit_payload
21
+ from reachy.cli._commands.overview import emit_overview
22
+ from reachy.robot.transport import DEFAULT_BASE_URL, DEFAULT_TIMEOUT
23
+
24
+ _JSON_HELP = "Emit structured JSON."
25
+
26
+ _VERBS = [
27
+ "daemon start — start the local reachy-mini-daemon in the background",
28
+ "daemon stop — stop the daemon this CLI started",
29
+ "daemon status — process + HTTP-health state of the daemon",
30
+ "daemon overview — this summary",
31
+ ]
32
+
33
+
34
+ def _add_health_args(parser: argparse.ArgumentParser) -> None:
35
+ """Flags shared by verbs that talk to the daemon's health route."""
36
+ parser.add_argument("--json", action="store_true", help=_JSON_HELP)
37
+ parser.add_argument(
38
+ "--base-url",
39
+ default=os.environ.get("REACHY_BASE_URL", DEFAULT_BASE_URL),
40
+ help="Daemon base URL for the health check (env REACHY_BASE_URL).",
41
+ )
42
+ parser.add_argument(
43
+ "--timeout",
44
+ type=float,
45
+ default=DEFAULT_TIMEOUT,
46
+ help=f"Health-check request timeout in seconds (default: {DEFAULT_TIMEOUT}).",
47
+ )
48
+
49
+
50
+ def cmd_daemon_overview(args: argparse.Namespace) -> int:
51
+ sections: list[dict[str, object]] = [
52
+ {"title": "Verbs", "items": list(_VERBS)},
53
+ {
54
+ "title": "State",
55
+ "items": [
56
+ f"pid file: {_daemon.pid_file()}",
57
+ f"log file: {_daemon.log_file()}",
58
+ f"health route: {DEFAULT_BASE_URL}{_daemon.HEALTH_PATH}",
59
+ ],
60
+ },
61
+ {
62
+ "title": "Conventions",
63
+ "items": [
64
+ "every command supports --json",
65
+ "start spawns reachy-mini-daemon detached, then polls the health route",
66
+ "the daemon ships in the [daemon] extra: pip install 'reachy-cli[daemon]'",
67
+ "override the binary with --daemon-cmd or REACHY_DAEMON_CMD",
68
+ "forward daemon args after '--' (e.g. -- --sim --fastapi-port 9000)",
69
+ "exit codes: 0 ok, 1 user error, 2 environment (binary/daemon missing)",
70
+ ],
71
+ },
72
+ ]
73
+ emit_overview(
74
+ "reachy-mini-cli daemon",
75
+ sections,
76
+ json_mode=bool(getattr(args, "json", False)),
77
+ )
78
+ return 0
79
+
80
+
81
+ def cmd_daemon_start(args: argparse.Namespace) -> int:
82
+ data = _daemon.start(
83
+ base_url=args.base_url,
84
+ wait=not args.no_wait,
85
+ wait_timeout=args.wait_timeout,
86
+ poll_timeout=args.timeout,
87
+ daemon_cmd=args.daemon_cmd,
88
+ extra_args=list(args.daemon_args or []),
89
+ )
90
+ emit_payload(data, json_mode=bool(getattr(args, "json", False)))
91
+ return 0
92
+
93
+
94
+ def cmd_daemon_stop(args: argparse.Namespace) -> int:
95
+ data = _daemon.stop(timeout=args.timeout)
96
+ emit_payload(data, json_mode=bool(getattr(args, "json", False)))
97
+ return 0
98
+
99
+
100
+ def cmd_daemon_status(args: argparse.Namespace) -> int:
101
+ data = _daemon.status(base_url=args.base_url, timeout=args.timeout)
102
+ emit_payload(data, json_mode=bool(getattr(args, "json", False)))
103
+ return 0
104
+
105
+
106
+ def _no_verb(args: argparse.Namespace) -> int:
107
+ return cmd_daemon_overview(args)
108
+
109
+
110
+ def register(sub: argparse._SubParsersAction) -> None:
111
+ p = sub.add_parser(
112
+ "daemon",
113
+ help="Local daemon process lifecycle (see 'reachy-mini-cli daemon overview').",
114
+ )
115
+ p.add_argument("--json", action="store_true", help=_JSON_HELP)
116
+ p.set_defaults(func=_no_verb, json=False)
117
+ noun_sub = p.add_subparsers(dest="daemon_command", parser_class=type(p))
118
+
119
+ ov = noun_sub.add_parser("overview", help="Describe the daemon noun group.")
120
+ ov.add_argument("--json", action="store_true", help=_JSON_HELP)
121
+ ov.set_defaults(func=cmd_daemon_overview)
122
+
123
+ start = noun_sub.add_parser("start", help="Start the local reachy-mini-daemon.")
124
+ _add_health_args(start)
125
+ start.add_argument(
126
+ "--daemon-cmd",
127
+ default=None,
128
+ help="Override the daemon launch command (env REACHY_DAEMON_CMD).",
129
+ )
130
+ start.add_argument(
131
+ "--no-wait",
132
+ action="store_true",
133
+ help="Return immediately after spawning, without polling the health route.",
134
+ )
135
+ start.add_argument(
136
+ "--wait-timeout",
137
+ type=float,
138
+ default=_daemon.DEFAULT_WAIT_TIMEOUT,
139
+ help="Seconds to wait for the daemon to become healthy "
140
+ f"(default: {_daemon.DEFAULT_WAIT_TIMEOUT:g}).",
141
+ )
142
+ start.add_argument(
143
+ "daemon_args",
144
+ nargs="*",
145
+ default=[],
146
+ metavar="-- ARGS",
147
+ help="Args after '--' are forwarded to reachy-mini-daemon "
148
+ "(e.g. -- --sim --fastapi-port 9000).",
149
+ )
150
+ start.set_defaults(func=cmd_daemon_start)
151
+
152
+ stop = noun_sub.add_parser("stop", help="Stop the daemon this CLI started.")
153
+ stop.add_argument("--json", action="store_true", help=_JSON_HELP)
154
+ stop.add_argument(
155
+ "--timeout",
156
+ type=float,
157
+ default=_daemon.DEFAULT_STOP_TIMEOUT,
158
+ help="Seconds to wait after SIGTERM before SIGKILL "
159
+ f"(default: {_daemon.DEFAULT_STOP_TIMEOUT:g}).",
160
+ )
161
+ stop.set_defaults(func=cmd_daemon_stop)
162
+
163
+ st = noun_sub.add_parser("status", help="Report daemon process + HTTP-health state.")
164
+ _add_health_args(st)
165
+ st.set_defaults(func=cmd_daemon_status)
@@ -31,6 +31,7 @@ Commands
31
31
  reachy-mini-cli cli overview Describe the CLI surface itself.
32
32
 
33
33
  Robot commands (talk to the Reachy daemon; --transport http|sdk)
34
+ reachy-mini-cli daemon start Start the local daemon; also: stop/status.
34
35
  reachy-mini-cli device status Daemon status / device info.
35
36
  reachy-mini-cli device state Live robot state (pose, antennas).
36
37
  reachy-mini-cli app list Available apps; also: status/start/stop.
@@ -66,6 +67,9 @@ def _as_json_payload() -> dict[str, object]:
66
67
  {"path": ["overview"], "summary": "Descriptive snapshot of the agent."},
67
68
  {"path": ["doctor"], "summary": "Check the agent-identity invariants."},
68
69
  {"path": ["cli", "overview"], "summary": "Describe the CLI surface."},
70
+ {"path": ["daemon", "start"], "summary": "Start the local reachy-mini-daemon."},
71
+ {"path": ["daemon", "stop"], "summary": "Stop the daemon this CLI started."},
72
+ {"path": ["daemon", "status"], "summary": "Daemon process + HTTP-health state."},
69
73
  {"path": ["device", "status"], "summary": "Daemon status / device info."},
70
74
  {"path": ["device", "state"], "summary": "Live robot state."},
71
75
  {"path": ["app", "list"], "summary": "List/start/stop Reachy Mini apps."},
@@ -30,6 +30,7 @@ _VERBS = [
30
30
  "explain <path> — markdown docs for a topic",
31
31
  "overview — this descriptive snapshot",
32
32
  "doctor — check the agent-identity invariants",
33
+ "daemon <verb> — start/stop/check the local reachy-mini-daemon process",
33
34
  "device <verb> — daemon/robot status and live state",
34
35
  "app <verb> — list/start/stop Reachy Mini apps",
35
36
  "move <verb> — runtime motion (goto, wake, sleep)",