Hermit is a document-first RFC collaboration application built for GitHub workflows.
It lets teams submit a single RFC markdown file in a pull request, review it in a rich reading experience, and collaborate with inline comments and approvals from the Hermit UI while preserving GitHub as the source of truth.
| Tool | Version | Notes |
|---|---|---|
| Go | 1.22+ | brew install go |
| Xcode | 16+ | Required for native app builds |
| Docker | any | Required for local Gitea |
| Node.js | 18+ | Required for web UI |
| Air | latest | go install github.com/air-verse/air@latest |
The make dev target does everything in one command — starts Gitea, seeds test data, installs the PAT to Keychain, builds the Go server, builds and launches the macOS app, and deploys to a connected iPad if configured.
make devAfter it completes:
- Gitea runs at
http://localhost:3000 - Hermit server runs at
http://localhost:8080 - HermitNative.app is open on macOS
On a fresh checkout, run these once before make dev:
cd ui && npm install
cd ..
open -a Dockermake dev uses full Xcode from /Applications/Xcode.app, even if
xcode-select points at Command Line Tools. The macOS app can build with
ad-hoc signing and an empty HERMIT_TEAM_ID; physical iPad deployment still
requires an Apple Development certificate and a provisioned device.
If Homebrew Python is broken or too new for local plist handling, use the default repo path:
make dev PYTHON=/usr/bin/python3This is also the Makefile default.
If make dev prints IPAD_UDID not set — skipping iPad deploy, the macOS app
was still built and launched. Configure .local.mk only when you want to deploy
to a connected physical iPad.
make gitea-upSeed a test repo and review-ready PR:
make gitea-seed-prLoad the generated token into your shell:
eval "$(cat .tmp/gitea-token-export.sh)"make build
HERMIT_PAT=$(cat .tmp/gitea-token.env | cut -d= -f2) bin/hermit serveFor live-reload development (Go + React hot reload):
make debugmake native-build-macosSeed config into UserDefaults so the app starts pre-configured:
make native-seed-prefsOpen the app:
open HermitNative.appOr build and launch in one step:
make native-openIf you prefer to build and run from Xcode rather than the command line:
open hermit-native/HermitNative.xcodeprojOr in Xcode: File → Open and select hermit-native/HermitNative.xcodeproj.
- In the Project Navigator select HermitNative (top of the tree)
- Select the HermitNative target → Signing & Capabilities tab
- Under Signing, check Automatically manage signing
- Set Team to your Apple ID or developer team
- Change the Bundle Identifier to something unique e.g.
com.yourname.hermit-native
For simulator-only builds no paid developer account is needed — a free personal team works.
Xcode needs a Local.xcconfig to know your bundle ID:
cp hermit-native/Local.xcconfig.example hermit-native/Local.xcconfigEdit Local.xcconfig and set HERMIT_BUNDLE_ID to match what you set in signing above.
- In the toolbar, click the destination picker (next to the scheme name)
- Under iOS Simulators pick iPad Pro 13-inch (M4) (or any iPad simulator)
- Press ⌘R to build and run
To run on a physical iPad:
- Connect the iPad via USB
- Accept the Trust This Computer prompt on the device
- Enable Developer Mode: Settings → Privacy & Security → Developer Mode
- Select the device in the Xcode destination picker
- Press ⌘R — Xcode will sign, install and launch automatically
After the first launch, the app needs server config. Run this once after make gitea-up:
make native-seed-prefsThen relaunch the app from Xcode (⌘R) or the simulator home screen.
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl create \
"iPad Pro 13-inch (M4)" \
"com.apple.CoreSimulator.SimDeviceType.iPad-Pro-13-inch-M4-8GB" \
"com.apple.CoreSimulator.SimRuntime.iOS-26-4"List available runtimes and device types if you need a different model:
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl list runtimes
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl list devicetypes | grep iPadDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl boot <UDID>
open -a Simulator# Build for simulator
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild \
-project hermit-native/HermitNative.xcodeproj \
-scheme HermitNative \
-destination "platform=iOS Simulator,id=<UDID>" \
-configuration Debug \
-derivedDataPath hermit-native/build \
EXCLUDED_SOURCE_FILE_NAMES="HermitServer.xcframework" \
OTHER_SWIFT_FLAGS="-DDEBUG" \
build
# Install on simulator
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl install \
<UDID> \
hermit-native/build/Build/Products/Debug-iphonesimulator/HermitNative.app
# Launch
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl launch \
<UDID> \
me.steven.hermit-nativeOr use the Makefile shortcut (builds and deploys by simulator name):
make native-build-ipad# Rebuild
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild \
-project hermit-native/HermitNative.xcodeproj \
-scheme HermitNative \
-destination "platform=iOS Simulator,id=<UDID>" \
-configuration Debug \
-derivedDataPath hermit-native/build \
EXCLUDED_SOURCE_FILE_NAMES="HermitServer.xcframework" \
OTHER_SWIFT_FLAGS="-DDEBUG" \
build
# Reinstall and relaunch
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl install <UDID> \
hermit-native/build/Build/Products/Debug-iphonesimulator/HermitNative.app
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcrun simctl launch <UDID> me.steven.hermit-nativeRequires an Apple Developer account and a provisioned device.
- Enable Developer Mode on the iPad: Settings → Privacy & Security → Developer Mode
- Trust this Mac when prompted on the device
- Copy the local config template:
cp .local.mk.example .local.mk- Edit
.local.mkand set:
IPAD_UDID = <device UDID from Xcode → Devices and Simulators>
IPAD_DEVICE_ID = <same UDID>make ipad-deployThis builds a signed Debug IPA and installs it via devicectl.
| Target | Description |
|---|---|
make dev |
Full zero-to-demo: Gitea + server + macOS app + iPad deploy |
make debug |
Live-reload: Air (Go) + Vite (React) |
make native-build |
Build for macOS and iPad simulator |
make native-build-macos |
Build macOS app only |
make native-build-ipad |
Build iPad simulator app only |
make native-open |
Build everything and launch macOS app |
make native-test |
Run Swift test suite |
make native-clean |
Remove build artifacts |
make ipad-deploy |
Build and deploy to physical iPad |
make gitea-up |
Start local Gitea container |
make gitea-seed-pr |
Seed test repo and PR |
make gitea-down |
Stop Gitea container |
make gitea-reset |
Destroy Gitea container and data |
make validate-config |
Validate hermit.yaml (structure + token + API access) |
make reset |
Full reset: kills app, destroys Gitea, wipes build artifacts |
Hermit is designed to make RFC review feel like Google Docs-style collaboration with GitHub-native governance.
Key capabilities:
- Single-file RFC pull request validation.
- Markdown rendering from the PR head branch.
- Inline anchored comments with thread lifecycle management.
- Approval actions from Hermit UI.
- Synchronization of comments and review state with GitHub.
- Backend: Go monolith.
- API: OpenAPI-first Hermit platform API.
- UI: React web application.
- Design system: HashiCorp Helios.
- Source of truth: GitHub PR state.
- Auth (initial): GitHub Personal Access Tokens (PAT).
docs-cms/- Product and architecture documentation (PRD, ADRs, RFCs)go.mod- Go module definition
Project planning and architecture decisions live in docs-cms/.
Core documents:
docs-cms/prd/prd-001-hermit-rfc-collaboration-vision.mddocs-cms/adr/adr-001-golang-base-application.mddocs-cms/adr/adr-002-single-monolith-application.mddocs-cms/adr/adr-003-github-source-of-truth.mddocs-cms/adr/adr-004-rfc-doc-source-and-format.mddocs-cms/adr/adr-005-use-pat-for-initial-github-authentication.mddocs-cms/adr/adr-006-adopt-hashicorp-helios-design-system.mddocs-cms/adr/adr-007-openapi-first-hermit-api-for-github-interactions.mddocs-cms/rfcs/rfc-001-hermit-high-level-design-and-architecture.mddocs-cms/rfcs/rfc-002-repository-configuration-and-pat-authentication.mddocs-cms/rfcs/rfc-003-openapi-platform-api-and-github-abstraction.mddocs-cms/rfcs/rfc-004-react-web-ui-with-helios-and-hermit-api.md
Use Docuchango to validate and manage docs:
docuchango validate --verboseIf you need help with docs-cms workflows:
docuchango bootstrap --guide agentHermit loads runtime config from config/hermit.yaml by default.
- Example file:
config/hermit.example.yaml - Default local file committed for development:
config/hermit.yaml - Optional override path:
HERMIT_CONFIG_FILE=/path/to/hermit.yaml
Registry entries allow multiple GitHub endpoints with token env references:
environment: development
listen_address: ":8080"
registries:
- name: github-public
kind: github
base_url: https://api.github.com
token_env_var: GITHUB_TOKEN
- name: github-enterprise
kind: github
base_url: https://github.example.com/api/v3
token_env_var: GHE_TOKEN
- name: gitea-local
kind: github
base_url: http://localhost:3000/api/v1
token_env_var: GITEA_TOKEN
repositories:
- owner: hashicorp
name: hermit
registry: github-public
default_branch: main
docs_path_policy: docs-cms/rfcs/
- owner: acme
name: platform-rfcs
registry: github-enterprise
default_branch: trunk
docs_path_policy: docs-cms/rfcs/
- owner: gitea_admin
name: hermit-rfcs
registry: gitea-local
default_branch: main
docs_path_policy: docs-cms/rfcs/Configured repositories are seeded at startup (when their token env var is set), and are available in the UI selection context.
hermitctl talks to the running Hermit server using the configured listen_address from config/hermit.yaml by default. Override the target with --addr or the config file with --config.
go run ./cmd/hermitctl health
go run ./cmd/hermitctl repo list
go run ./cmd/hermitctl repo get repo_2001
go run ./cmd/hermitctl repo validate repo_2001
go run ./cmd/hermitctl repo debug repo_2001Add a repository with a token from a secure prompt:
go run ./cmd/hermitctl repo add \
--owner gitea_admin \
--name hermit-rfcs \
--registry gitea-localFor automation, pass tokens on stdin so they do not appear in shell history:
printf '%s' "$GITEA_TOKEN" | go run ./cmd/hermitctl repo rotate-token --token-stdin repo_2001You can also rotate from a .env file without exporting the token into the shell:
go run ./cmd/hermitctl repo rotate-token --env-file .env --token-env GITEA_TOKEN repo_2001If --token-env is omitted with --env-file, hermitctl reads HERMIT_PAT.
If your Git credential helper already stores the token, hermitctl can read it without exporting anything:
go run ./cmd/hermitctl repo add \
--owner jrepp \
--name z \
--registry github-enterprise \
--git-credential \
--credential-host github.ibm.comNative app repository lists can be exported as token-free JSON and shared with the team. The export includes account endpoints and repository metadata only; PATs and git credential helper state are intentionally local to each developer.
go run ./cmd/hermitctl repo export-local --output hermit-repos.jsonAnother developer can import that file without receiving any credentials:
go run ./cmd/hermitctl repo import-local --file hermit-repos.jsonAfter import, each developer binds their own local credential for the account endpoint. For GitHub Enterprise with the git credential helper:
go run ./cmd/hermitctl repo bind-credential \
--endpoint https://github.ibm.com/api/v3 \
--git-credential \
--credential-host github.ibm.comFor automation, bind from stdin instead:
printf '%s' "$GHE_TOKEN" | go run ./cmd/hermitctl repo bind-credential \
--endpoint https://github.ibm.com/api/v3 \
--token-stdinUse --json on any server command for machine-readable output.
Validate config locally:
make validate-config
make validate-config-structurevalidate-configperforms full validation (structure + token presence + repository API access).validate-config-structurechecks only file structure and required fields.
Use the built-in Make targets to run a local Gitea instance for integration testing.
make gitea-up- Web UI:
http://localhost:3000 - SSH:
localhost:2222 - Persistent data directory:
./data/gitea/
Additional commands:
make gitea-seed-pr # (re)seed repo + review-ready PR
make gitea-logs # stream container logs
make gitea-down # stop container
make gitea-reset # remove container and delete ./data/gitea/Convenience scripts at repo root:
./run.sh # gitea-down -> gitea-up (with retry/delay) -> make run
./run.sh --debug # gitea-down -> gitea-up (with retry/delay) -> make debug (Air + Vite)
./ren.sh --debug # alias for ./run.sh --debug
./stop.sh # make gitea-down
./stop.sh --reset # make gitea-down && make gitea-resetTo use Gitea with Hermit config, set GITEA_TOKEN and use registry base URL http://localhost:3000/api/v1.
After make gitea-up, set your current shell token with:
eval "$(cat .tmp/gitea-token-export.sh)"make gitea-up now automatically ensures a valid local token and prints an eval command you can run to load GITEA_TOKEN into your current shell session.
Thread/comment state is now persisted across Hermit restarts at ./data/hermit/threads.json.
Seed details (make gitea-seed-pr):
- Creates admin user:
gitea_admin/gitea_admin(local test only) - Creates repo:
gitea_admin/hermit-rfcs - Pushes main-branch RFC:
docs-cms/rfcs/rfc-001-seeded-main-branch.md - Pushes PR branch RFC:
docs-cms/rfcs/rfc-002-seeded-pr-review.md - Opens ready-for-review PR from
feat/rfc-002-seeded-pr-review->main
Use make debug for live-reload development:
- Starts Go backend with Air (
cmd/hermit) and rebuilds/restarts on backend file changes. - Starts Vite in
ui/so UI changes hot-reload automatically. - Backend runs on configured Hermit address (default
http://localhost:8080), UI dev server runs onhttp://localhost:4173.
Prerequisites:
go install github.com/air-verse/air@latestThen run:
make debugHermit is currently in planning/design phase with foundational PRD, ADRs, and RFCs in place.