hermes-local-hands 0.1.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 (38) hide show
  1. hermes_local_hands-0.1.0/.github/workflows/ci.yml +58 -0
  2. hermes_local_hands-0.1.0/.github/workflows/codeql.yml +38 -0
  3. hermes_local_hands-0.1.0/.github/workflows/publish.yml +89 -0
  4. hermes_local_hands-0.1.0/.gitignore +18 -0
  5. hermes_local_hands-0.1.0/CHANGELOG.md +30 -0
  6. hermes_local_hands-0.1.0/CONTRIBUTING.md +52 -0
  7. hermes_local_hands-0.1.0/LICENSE +21 -0
  8. hermes_local_hands-0.1.0/PKG-INFO +375 -0
  9. hermes_local_hands-0.1.0/README.md +320 -0
  10. hermes_local_hands-0.1.0/ROADMAP.md +43 -0
  11. hermes_local_hands-0.1.0/SECURITY.md +52 -0
  12. hermes_local_hands-0.1.0/THREAT_MODEL.md +130 -0
  13. hermes_local_hands-0.1.0/docs/ADR-001-ARCHITECTURE.md +69 -0
  14. hermes_local_hands-0.1.0/docs/SERVICE.md +113 -0
  15. hermes_local_hands-0.1.0/pyproject.toml +74 -0
  16. hermes_local_hands-0.1.0/src/hermes_local_hands/__init__.py +17 -0
  17. hermes_local_hands-0.1.0/src/hermes_local_hands/__main__.py +6 -0
  18. hermes_local_hands-0.1.0/src/hermes_local_hands/auth.py +89 -0
  19. hermes_local_hands-0.1.0/src/hermes_local_hands/cli.py +504 -0
  20. hermes_local_hands-0.1.0/src/hermes_local_hands/errors.py +33 -0
  21. hermes_local_hands-0.1.0/src/hermes_local_hands/gitops.py +1201 -0
  22. hermes_local_hands-0.1.0/src/hermes_local_hands/hermes_config.py +142 -0
  23. hermes_local_hands-0.1.0/src/hermes_local_hands/mcp_server.py +404 -0
  24. hermes_local_hands-0.1.0/src/hermes_local_hands/models.py +69 -0
  25. hermes_local_hands-0.1.0/src/hermes_local_hands/paths.py +134 -0
  26. hermes_local_hands-0.1.0/src/hermes_local_hands/receipts.py +244 -0
  27. hermes_local_hands-0.1.0/src/hermes_local_hands/service.py +959 -0
  28. hermes_local_hands-0.1.0/src/hermes_local_hands/storage.py +840 -0
  29. hermes_local_hands-0.1.0/tests/test_cli_interface.py +376 -0
  30. hermes_local_hands-0.1.0/tests/test_core_receipts.py +17 -0
  31. hermes_local_hands-0.1.0/tests/test_core_service.py +114 -0
  32. hermes_local_hands-0.1.0/tests/test_gitops_security.py +609 -0
  33. hermes_local_hands-0.1.0/tests/test_mcp_adapter.py +198 -0
  34. hermes_local_hands-0.1.0/tests/test_mcp_e2e.py +171 -0
  35. hermes_local_hands-0.1.0/tests/test_paths_security.py +123 -0
  36. hermes_local_hands-0.1.0/tests/test_service_workflow.py +670 -0
  37. hermes_local_hands-0.1.0/tests/test_storage_receipts.py +358 -0
  38. hermes_local_hands-0.1.0/uv.lock +1791 -0
@@ -0,0 +1,58 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ test:
17
+ name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
18
+ runs-on: ${{ matrix.os }}
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ os: [ubuntu-latest, macos-latest]
23
+ python-version: ['3.11', '3.12', '3.13']
24
+ steps:
25
+ - name: Check out repository
26
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ persist-credentials: false
29
+ - name: Set up Python
30
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ cache: pip
34
+ - name: Upgrade the environment installer
35
+ run: >-
36
+ python -m pip install --disable-pip-version-check --upgrade
37
+ 'pip>=26.2' 'setuptools>=83'
38
+ - name: Install project
39
+ run: python -m pip install --disable-pip-version-check -e '.[dev]'
40
+ - name: Lint
41
+ run: ruff check .
42
+ - name: Check formatting
43
+ run: ruff format --check .
44
+ - name: Audit runtime dependencies
45
+ run: pip-audit --local --skip-editable --progress-spinner off
46
+ - name: Test
47
+ run: pytest --cov=hermes_local_hands --cov-report=term-missing
48
+ - name: Build distributions
49
+ run: python -m build
50
+ - name: Validate package metadata
51
+ run: python -m twine check dist/*
52
+ - name: Install built wheel in a clean environment
53
+ shell: bash
54
+ run: |
55
+ clean_venv="$(mktemp -d)/wheel-smoke"
56
+ python -m venv "$clean_venv"
57
+ "$clean_venv/bin/python" -m pip install --disable-pip-version-check dist/*.whl
58
+ "$clean_venv/bin/hermes-local-hands" --help
@@ -0,0 +1,38 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: '23 4 * * 1'
10
+
11
+ permissions:
12
+ contents: read
13
+ security-events: write
14
+
15
+ concurrency:
16
+ group: codeql-${{ github.workflow }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ analyze:
21
+ name: Python
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 15
24
+ steps:
25
+ - name: Check out repository
26
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ persist-credentials: false
29
+ - name: Initialize CodeQL
30
+ uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
31
+ with:
32
+ languages: python
33
+ build-mode: none
34
+ config: |
35
+ paths:
36
+ - src/hermes_local_hands
37
+ - name: Analyze
38
+ uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
@@ -0,0 +1,89 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions: {}
8
+
9
+ concurrency:
10
+ group: publish-${{ github.event.release.tag_name }}
11
+ cancel-in-progress: false
12
+
13
+ jobs:
14
+ build:
15
+ name: Build and verify distributions
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - name: Check out the released tag
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+ with:
23
+ ref: ${{ github.event.release.tag_name }}
24
+ persist-credentials: false
25
+ - name: Set up Python
26
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
+ with:
28
+ python-version: "3.13"
29
+ - name: Upgrade the environment installer
30
+ run: >-
31
+ python -m pip install --disable-pip-version-check --upgrade
32
+ 'pip>=26.2' 'setuptools>=83'
33
+ - name: Verify release tag and package version
34
+ env:
35
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
36
+ run: |
37
+ python - <<'PY'
38
+ import os
39
+ import tomllib
40
+ from pathlib import Path
41
+
42
+ version = tomllib.loads(
43
+ Path("pyproject.toml").read_text(encoding="utf-8")
44
+ )["project"]["version"]
45
+ expected = f"v{version}"
46
+ if os.environ["RELEASE_TAG"] != expected:
47
+ raise SystemExit(
48
+ f"release tag must be {expected!r}, got {os.environ['RELEASE_TAG']!r}"
49
+ )
50
+ PY
51
+ - name: Install release checks
52
+ run: python -m pip install --disable-pip-version-check -e '.[dev]'
53
+ - name: Validate source
54
+ run: |
55
+ ruff check .
56
+ ruff format --check .
57
+ pytest
58
+ - name: Build distributions
59
+ run: python -m build
60
+ - name: Validate package metadata
61
+ run: python -m twine check dist/*
62
+ - name: Store distributions
63
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
64
+ with:
65
+ name: python-package-distributions
66
+ path: dist/
67
+ if-no-files-found: error
68
+ retention-days: 7
69
+
70
+ publish:
71
+ name: Publish distributions
72
+ if: vars.PYPI_PUBLISH == 'true'
73
+ needs: [build]
74
+ runs-on: ubuntu-latest
75
+ environment:
76
+ name: pypi
77
+ url: https://pypi.org/p/hermes-local-hands
78
+ permissions:
79
+ id-token: write
80
+ steps:
81
+ - name: Retrieve distributions
82
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
83
+ with:
84
+ name: python-package-distributions
85
+ path: dist/
86
+ - name: Publish distributions with attestations
87
+ uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
88
+ with:
89
+ attestations: true
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .coverage
5
+ .coverage.*
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ venv/
10
+ dist/
11
+ build/
12
+ *.log
13
+ .DS_Store
14
+ .env
15
+ .env.*
16
+ !.env.example
17
+ hermes-local-hands-data/
18
+ .local-hands/
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ - No stable protocol, production-security claim, hosted service, or compatibility
6
+ guarantee is made until a release explicitly says otherwise.
7
+
8
+ ## 0.1.0 - 2026-09-04
9
+
10
+ Initial alpha release candidate:
11
+
12
+ - loopback-only MCP companion with explicit trusted reverse-proxy host settings;
13
+ - client-scoped workspace grants and narrow status/file-read tools;
14
+ - pending, expiring patch/check requests with local-only approval and rejection;
15
+ - snapshot-only patch/check execution, stale-request protection, and uncertain
16
+ recovery after interrupted execution;
17
+ - anonymous archives, high-entropy staging, and atomic no-replace snapshot
18
+ publication on supported macOS and Linux hosts;
19
+ - exact-ID snapshot cleanup with fd-bound deletion, retained completion markers,
20
+ and explicit incomplete-deletion visibility;
21
+ - signed receipt chain and local verification command;
22
+ - source/build/test/package validation automation.
23
+ - one real two-machine alpha validation over tailnet HTTPS, covering remote
24
+ status/read, a pending patch, local approval, snapshot execution, a linked
25
+ check, and receipt verification.
26
+
27
+ This version is not a sandbox. Approved checks run trusted repository code with
28
+ the local user's normal host and network access and can cause host-side effects.
29
+ Its post-check source observation covers Git-visible checkout status only. It
30
+ has not established broad Hermes/tunnel compatibility or production readiness.
@@ -0,0 +1,52 @@
1
+ # Contributing
2
+
3
+ Thanks for helping test a safer local companion for Hermes. The project is
4
+ alpha-stage; its protocol, state format, and CLI can change before a stable
5
+ release.
6
+
7
+ ## Development setup and required checks
8
+
9
+ ```bash
10
+ python3.11 -m venv .venv
11
+ source .venv/bin/activate
12
+ python -m pip install -e '.[dev]'
13
+
14
+ ruff check .
15
+ ruff format --check .
16
+ pip-audit --local --skip-editable --progress-spinner off
17
+ pytest --cov=hermes_local_hands --cov-report=term-missing
18
+ python -m build
19
+ python -m twine check dist/*
20
+ ```
21
+
22
+ Run checks against the commit you propose. Do not claim a command passed on a
23
+ platform where you did not run it. CI also installs the built wheel into a clean
24
+ environment; that validates packaging, not a real tunnel or two-host Hermes
25
+ deployment.
26
+
27
+ ## Security-sensitive changes
28
+
29
+ Any change to authentication, client/workspace grants, paths, secret handling,
30
+ patch parsing, approvals, request TTL/recovery, receipts, command execution,
31
+ proxy trust, or state persistence requires all of the following:
32
+
33
+ 1. A focused test for success and fail-closed behavior.
34
+ 2. A [THREAT_MODEL.md](THREAT_MODEL.md) update when the trust boundary changes.
35
+ 3. A review of whether output, errors, logs, or receipts could leak local paths,
36
+ credentials, or source.
37
+ 4. A clear statement of what remains untrusted. In particular, fixed check
38
+ profiles execute repository code as the local user, are not a sandbox, and
39
+ can cause host-side effects. Git-status observation does not detect effects
40
+ outside the checkout's Git-visible state.
41
+
42
+ Prefer a smaller denied surface over a convenient implicit fallback. Preserve
43
+ the invariant that the remote protocol can neither approve/deny nor write the
44
+ active checkout, merge, push, or expose a raw shell.
45
+
46
+ ## Pull requests
47
+
48
+ Describe the user problem, scope, security impact, validation actually run, and
49
+ known limitations. Keep changes narrow. Do not include automatic merge/push,
50
+ credentials, private repositories, private state databases, bearer tokens, or
51
+ real action receipts. Report security problems privately under
52
+ [SECURITY.md](SECURITY.md).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maurice Mohr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,375 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermes-local-hands
3
+ Version: 0.1.0
4
+ Summary: A consent-gated local companion for remote Hermes agents
5
+ Project-URL: Homepage, https://github.com/mauricemohr88-debug/hermes-local-hands
6
+ Project-URL: Documentation, https://github.com/mauricemohr88-debug/hermes-local-hands#readme
7
+ Project-URL: Issues, https://github.com/mauricemohr88-debug/hermes-local-hands/issues
8
+ Project-URL: Source, https://github.com/mauricemohr88-debug/hermes-local-hands
9
+ Author: Maurice Mohr
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Maurice Mohr
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: agents,automation,git,hermes,mcp,security
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: MacOS
37
+ Classifier: Operating System :: POSIX :: Linux
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Software Development :: Libraries
42
+ Requires-Python: >=3.11
43
+ Requires-Dist: cryptography<51,>=50
44
+ Requires-Dist: mcp<3,>=2.1
45
+ Requires-Dist: uvicorn<1,>=0.35
46
+ Provides-Extra: dev
47
+ Requires-Dist: build<2,>=1.2; extra == 'dev'
48
+ Requires-Dist: httpx<1,>=0.28; extra == 'dev'
49
+ Requires-Dist: pip-audit<3,>=2.10; extra == 'dev'
50
+ Requires-Dist: pytest-cov<7,>=6; extra == 'dev'
51
+ Requires-Dist: pytest<10,>=9.0.3; extra == 'dev'
52
+ Requires-Dist: ruff<1,>=0.12; extra == 'dev'
53
+ Requires-Dist: twine<7,>=6; extra == 'dev'
54
+ Description-Content-Type: text/markdown
55
+
56
+ # Hermes Local Hands
57
+
58
+ [![CI](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/ci.yml/badge.svg)](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/ci.yml)
59
+ [![CodeQL](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/codeql.yml/badge.svg)](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/codeql.yml)
60
+ [![PyPI](https://img.shields.io/pypi/v/hermes-local-hands.svg)](https://pypi.org/project/hermes-local-hands/)
61
+
62
+ Hermes Local Hands is an **alpha** local companion for a remote
63
+ [Hermes](https://github.com/NousResearch/hermes-agent) agent. It gives that
64
+ agent a deliberately narrow view of one registered repository, while the
65
+ computer that owns the repository retains the final say over every change and
66
+ check.
67
+
68
+ It solves a concrete split-machine problem: Hermes can reason on a Mac Studio,
69
+ server, or VM, while the developer's source code remains on a different local
70
+ machine. The remote side can inspect approved files and create requests. It
71
+ cannot run a shell, approve or deny a request, write the active checkout,
72
+ merge, or push.
73
+
74
+ > **Security boundary in v0.1.** `workspace_status` and `read_file` are remote
75
+ > inspection tools. `propose_patch` and `request_check` only create a pending,
76
+ > expiring request. Approval and rejection are local-operator actions. Local
77
+ > Hands applies patches and launches checks from an isolated snapshot rather
78
+ > than writing the registered checkout itself. Approved check code still has
79
+ > the local user's normal host and network access and can deliberately modify
80
+ > the registered checkout; this is **not** a sandbox.
81
+
82
+ ## Why this now
83
+
84
+ The architecture follows real upstream demand for safer local execution and
85
+ split-runtime workflows, rather than adding a second Hermes runtime. Useful
86
+ primary context is the upstream discussions [#18715](https://github.com/NousResearch/hermes-agent/issues/18715),
87
+ [#42807](https://github.com/NousResearch/hermes-agent/issues/42807), and
88
+ [#16462](https://github.com/NousResearch/hermes-agent/issues/16462), plus the
89
+ related implementation work in [#63966](https://github.com/NousResearch/hermes-agent/pull/63966)
90
+ and [#43045](https://github.com/NousResearch/hermes-agent/pull/43045).
91
+
92
+ ## What it does — and does not do
93
+
94
+ | Capability | Remote Hermes can do it | Local operator must do it |
95
+ | --- | --- | --- |
96
+ | See sanitised Git status | Yes, for a granted workspace | Register the workspace and grant the client |
97
+ | Read a text file | Yes, only inside the explicit read allowlist | Define the allowlist |
98
+ | Propose a textual patch | Create a pending request only | Review and approve it |
99
+ | Request a fixed check profile | Create a pending request only | Review and approve it |
100
+ | Apply or test | No | Local Hands launches it in a snapshot; approved code remains host-capable |
101
+ | Reject, merge, push, use a shell | No | Reject is local; merge/push/shell are outside the protocol |
102
+
103
+ Every request is tied to an authenticated client, an allowed workspace, the
104
+ current Git `HEAD`, the workspace-policy hash, an idempotency key scoped to the
105
+ client, and a short TTL. A changed policy or commit invalidates a request.
106
+ Re-registering an existing workspace ID replaces its client grants with exactly
107
+ the new `--client` list; old grants do not follow a changed root or policy.
108
+ Interrupted execution is recorded as **uncertain**, not silently reported as a
109
+ success. The local state store also writes a signed, append-only receipt chain
110
+ for requests and operator decisions.
111
+
112
+ ## Install and local-only quickstart
113
+
114
+ Requirements: Python 3.11+ and Git. Install the isolated command with
115
+ [uv](https://docs.astral.sh/uv/) or pipx:
116
+
117
+ ```bash
118
+ uv tool install hermes-local-hands
119
+ # Alternative: pipx install hermes-local-hands
120
+ ```
121
+
122
+ For development from a source checkout instead:
123
+
124
+ ```bash
125
+ # Use a Python 3.11+ executable; macOS /usr/bin/python3 may still be too old.
126
+ python3.11 -m venv .venv
127
+ source .venv/bin/activate
128
+ python -m pip install -e '.[dev]'
129
+ ```
130
+
131
+ The example below uses `demo` and only exposes `src` and `tests`; replace the
132
+ absolute path and allowlist with your own deliberate choices.
133
+
134
+ ```bash
135
+
136
+ # Creates private local state and a bearer credential file (mode 0600).
137
+ hermes-local-hands init --client-id hermes-mac-studio
138
+
139
+ # Register one Git repository and fixed, named check profiles.
140
+ hermes-local-hands workspace add \
141
+ --id demo \
142
+ --root /absolute/path/to/repository \
143
+ --read src \
144
+ --read tests \
145
+ --write src \
146
+ --check syntax=/usr/bin/python3,-m,compileall,-q,src \
147
+ --client hermes-mac-studio
148
+
149
+ # Start the listener. It refuses non-loopback bind addresses.
150
+ hermes-local-hands serve --host 127.0.0.1 --port 8741
151
+ ```
152
+
153
+ `init` stores the token under the private state directory rather than printing
154
+ it. On macOS/Linux the default is
155
+ `~/.local/state/hermes-local-hands/clients/<client-id>.token`; set
156
+ `XDG_STATE_HOME` first to choose another private state location. Read the file
157
+ locally and export it only in the Hermes runtime's private environment:
158
+
159
+ ```bash
160
+ export HERMES_LOCAL_HANDS_TOKEN="$(< "$HOME/.local/state/hermes-local-hands/clients/hermes-mac-studio.token")"
161
+ ```
162
+
163
+ Do not put a real token in a repository, issue, screenshot, shell history, or
164
+ public configuration file.
165
+
166
+ ### End-to-end workflow
167
+
168
+ 1. The local operator registers a workspace, its explicit read/write allowlist,
169
+ fixed check profiles, and the Hermes client grant.
170
+ `workspace grants <workspace-id>` lists current grants; `workspace revoke
171
+ <workspace-id> <client-id>` removes one and records the decision.
172
+ 2. The local operator starts `serve` on loopback only.
173
+ 3. A trusted reverse proxy/tunnel terminates HTTPS and forwards to that local
174
+ listener. Local Hands still accepts only the exact proxy host(s) named at
175
+ startup; it does not infer trust from arbitrary forwarded headers.
176
+ 4. Hermes calls `workspace_status` or `read_file`, or creates a pending patch
177
+ or check request.
178
+ Patch and check requests require a clean registered checkout both when they
179
+ are created and when they are approved; uncommitted changes are never copied
180
+ into the managed snapshot.
181
+ 5. The local operator inspects the request and chooses one of these local-only
182
+ commands:
183
+
184
+ ```bash
185
+ hermes-local-hands request list --state pending
186
+ # Default output is a multiline, control-escaped local review.
187
+ hermes-local-hands request show <request-id>
188
+ # Copy the approval_code shown above; it is intentionally request-specific.
189
+ hermes-local-hands request approve <request-id> --confirm <approval-code>
190
+ # or:
191
+ hermes-local-hands request deny <request-id> --reason "not approved"
192
+ ```
193
+
194
+ Use `request show <request-id> --json` only when escaped machine-readable
195
+ output is needed. The default review prefixes every untrusted patch line and
196
+ escapes terminal/bidirectional controls so a patch cannot visually imitate
197
+ the approval fields.
198
+
199
+ 6. An approved operation is revalidated against the recorded `HEAD` and policy,
200
+ then Local Hands applies or launches it from a managed snapshot. Approved
201
+ check code is still host-capable. Inspect the result with
202
+ `hermes-local-hands request status <request-id>` and verify the receipt chain
203
+ locally with `hermes-local-hands receipt-verify`.
204
+
205
+ 7. After reviewing a succeeded or failed snapshot, free its retention slot only
206
+ through the exact-ID local deletion gate:
207
+
208
+ ```bash
209
+ hermes-local-hands snapshot list
210
+ hermes-local-hands snapshot delete <request-id> --confirm <request-id>
211
+ ```
212
+
213
+ The command records deletion-requested and deletion-completed receipts. It
214
+ refuses pending, executing, missing, symlinked, or mismatched targets and
215
+ never deletes snapshots automatically. An `uncertain` snapshot can be
216
+ removed only after explicit local review and the same exact-ID confirmation.
217
+ A successful deletion empties the already-open request directory in place
218
+ and retains only a tiny completion marker instead of performing a final
219
+ path-based directory removal. Completed marker directories are omitted from
220
+ `snapshot list`; incomplete deletions remain visible there for local
221
+ investigation.
222
+
223
+ For a check request linked to an approved patch, create it locally with the
224
+ patch request ID. The check then uses that patch's approved snapshot rather
225
+ than the mutable active checkout:
226
+
227
+ ```bash
228
+ hermes-local-hands check add demo syntax check-after-patch-001 \
229
+ --client hermes-mac-studio \
230
+ --patch-request <approved-patch-request-id>
231
+ ```
232
+
233
+ That approval creates a **fresh** snapshot at the recorded base commit and
234
+ replays the exact stored patch bytes whose digest was reviewed; it does not
235
+ reuse a mutable previous snapshot. A check result is evidence about that
236
+ snapshot only; it is not a merge, deployment, or production safety claim.
237
+
238
+ ## Hermes MCP configuration
239
+
240
+ Generate the configuration fragment rather than hand-copying names:
241
+
242
+ ```bash
243
+ # Local same-machine use:
244
+ hermes-local-hands hermes-config --port 8741
245
+
246
+ # Remote use: pass the exact, already configured HTTPS tunnel endpoint.
247
+ hermes-local-hands hermes-config \
248
+ --endpoint https://mac-studio.example.ts.net/mcp \
249
+ --token-env HERMES_LOCAL_HANDS_TOKEN
250
+ ```
251
+
252
+ The generated YAML includes only these five tools:
253
+
254
+ - `workspace_status`
255
+ - `read_file`
256
+ - `propose_patch`
257
+ - `request_check`
258
+ - `request_status`
259
+
260
+ The two inspection tools advertise the MCP `readOnlyHint`. Hermes versions and
261
+ clients may still apply their own approval or policy gate to those tools; the
262
+ hint is useful metadata, not a compatibility guarantee or a bypass.
263
+
264
+ Hermes releases affected by upstream issue
265
+ [#88858](https://github.com/NousResearch/hermes-agent/issues/88858) may still
266
+ prompt for every read-only call while `trust: untrusted` is configured. That is
267
+ a fail-closed Hermes client behaviour, not additional Local Hands authority.
268
+ The upstream fix is tracked in
269
+ [#88372](https://github.com/NousResearch/hermes-agent/pull/88372). Keep the
270
+ generated `untrusted` setting unless you have reviewed the implications of
271
+ changing the client-side trust policy.
272
+
273
+ For a reverse proxy, bind Local Hands only to loopback and name every permitted
274
+ external Host exactly:
275
+
276
+ ```bash
277
+ hermes-local-hands serve --host 127.0.0.1 --port 8741 \
278
+ --proxy-host mac-studio.example.ts.net
279
+ ```
280
+
281
+ The endpoint generator accepts loopback `http://.../mcp` or an explicit
282
+ non-loopback `https://.../mcp` URL. It rejects embedded credentials, query
283
+ strings, and unsafe schemes. Configure the tunnel's own identity,
284
+ authentication, and HTTPS separately; do not expose port 8741 directly to the
285
+ public Internet.
286
+
287
+ ## Safety properties and limits
288
+
289
+ - **Explicit scope:** a client must have a workspace grant; path reads and
290
+ patches must stay inside the workspace allowlist. Secret-looking and binary
291
+ material is not returned as normal text.
292
+ - **No direct mutation:** no remote endpoint approves, denies, executes a
293
+ shell, writes the registered checkout, merges, or pushes.
294
+ - **Snapshot working directory:** accepted patches and checks use a snapshot
295
+ rooted at the recorded commit and do not include uncommitted checkout
296
+ changes. This constrains Local Hands' own file operations, not what approved
297
+ check code can access on the host.
298
+ - **Checks are not sandboxed:** fixed profiles limit what the protocol launches,
299
+ but the selected program still runs as the local user. It can access that
300
+ user's files, network, credentials, services, and can cause host-side
301
+ effects. Only approve profiles and repositories you trust.
302
+ - **Checkout observation is limited:** after a check, Local Hands compares only
303
+ Git-visible checkout status before and after. `same` does not prove that no
304
+ non-Git file, service, network, credential, or other host-side effect
305
+ occurred.
306
+ - **Audit evidence:** retained request/decision events form a signed receipt
307
+ chain. Verification detects edits and reordering within that retained chain;
308
+ without an externally anchored head it cannot detect deletion of a valid
309
+ tail or rollback to an earlier valid database. Receipts also do not prove a
310
+ host was not compromised.
311
+ - **Bounded alpha retention:** v0.1 permits at most 32 open requests per client,
312
+ 256 open requests globally, 512 retained requests per client, and 2,048
313
+ retained requests globally. Managed storage also permits at most 128 snapshot
314
+ deletion-marker directories. It never silently deletes requests, snapshots,
315
+ or markers.
316
+ Reviewed terminal snapshots can be removed one at a time through the local
317
+ exact-ID command above, but there is no request-record or marker-prune command
318
+ yet. Reaching a retained-request or deletion-marker cap is a deliberate
319
+ fail-closed stop that needs a later reviewed retention/migration release, not
320
+ a database or filesystem deletion workaround.
321
+ - **Failed creation markers:** snapshot construction uses a private,
322
+ high-entropy `.creating-*` staging directory and atomic no-replace publication.
323
+ If construction fails, Local Hands clears only the directory bound to its open
324
+ descriptor and deliberately does not perform a race-prone path deletion. An
325
+ empty or partially cleared staging marker can therefore remain and counts
326
+ conservatively toward the 32-snapshot limit. v0.1 has no CLI prune operation
327
+ for these internal markers; repeated build failures require a reviewed
328
+ recovery/migration rather than manual deletion while evidence matters.
329
+
330
+ Read [THREAT_MODEL.md](THREAT_MODEL.md) and [SECURITY.md](SECURITY.md) before
331
+ using it with sensitive repositories.
332
+
333
+ ## Development validation
334
+
335
+ ```bash
336
+ ruff check .
337
+ ruff format --check .
338
+ pip-audit --local --skip-editable --progress-spinner off
339
+ pytest --cov=hermes_local_hands --cov-report=term-missing
340
+ python -m build
341
+ python -m twine check dist/*
342
+ ```
343
+
344
+ CI additionally installs the built wheel into a clean environment. These are
345
+ release checks for the package, not proof of a safe production rollout.
346
+
347
+ On 2026-09-07 one real two-machine alpha flow was completed using Hermes on one
348
+ Mac, this service on another Mac, and HTTPS over a private Tailscale network.
349
+ The run covered unauthenticated rejection, status and file reads, a pending
350
+ patch, local approval, snapshot application, a linked check, and receipt-chain
351
+ verification. This is evidence for that exact environment only; it is not a
352
+ general compatibility, availability, or production-security claim.
353
+
354
+ For a durable local service setup, see [docs/SERVICE.md](docs/SERVICE.md).
355
+
356
+ ## Project direction
357
+
358
+ The security core is intended to remain free and open source. There is no paid
359
+ plan, hosted service, customer, or revenue today. Adoption and safety come
360
+ before any optional convenience layer. See [ROADMAP.md](ROADMAP.md) for the
361
+ public, evidence-gated direction.
362
+
363
+ ## Contributing and security
364
+
365
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development expectations and
366
+ [SECURITY.md](SECURITY.md) for private vulnerability reporting. Never put
367
+ credentials, private source, or real action receipts in a public issue or pull
368
+ request.
369
+
370
+ ## License
371
+
372
+ Released under the [MIT License](LICENSE).
373
+
374
+ Hermes Local Hands is an independent community project and is not affiliated
375
+ with or endorsed by Nous Research.