generated from eblume/project-template
feat(cli): heph daemon — manage hephd as a launchd/systemd service
Some checks failed
Build / validate (pull_request) Has been cancelled
Some checks failed
Build / validate (pull_request) Has been cancelled
Surfaces are connect-only; the daemon now runs as an explicit OS service so it can be shared without any surface owning its lifecycle. - service.rs: heph daemon start/stop/restart/status/uninstall, idempotent; launchd LaunchAgent (macOS) / systemd user service (Linux); resolves hephd next to heph else on PATH; pure plist/unit render fns unit-tested - main.rs: Command::Daemon handled before connecting (like auth) - hephd: default socket is now a STABLE <data-dir>/heph/hephd.sock when XDG_RUNTIME_DIR is unset (was $TMPDIR — fragile for a persistent service; macOS prunes /var/folders and the path varied per session) - tech-spec §14: CLI + daemon-service done entries Verified live on macOS: start/restart/stop/uninstall + CLI reaches the store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1315b9ce18
commit
0cfe627055
4 changed files with 443 additions and 6 deletions
|
|
@ -358,7 +358,9 @@ See [[design]] §5–§7 for the constraints later phases impose on present choi
|
|||
- ✅ **`client` mode + `RemoteStore` (§3.1, slice 9b):** a no-replica backend that proxies every `Store` call to a `server`'s `POST /rpc` (the full `dispatch`, over HTTP) via a **blocking** reqwest client — the online-only escape hatch. `Daemon` is now generic over `dyn Store + Send`, so the same unix-socket surface fronts either a `LocalStore` or a `RemoteStore`. Sync primitives are stubbed (a client has no op-log). Proven in `tests/client_mode.rs`. `dispatch` gained `task.get` + `links.add`.
|
||||
- ✅ **Hub auth — verification side (§13, slice 10a):** the hub validates an **OIDC bearer token** (`jsonwebtoken`, RS256-pinned, exact iss+aud, exp/nbf, required `sub`; JWKS discovered + cached, refetched on unknown `kid`) on `/sync/*` + `/rpc`. A [`TokenVerifier`] trait seam keeps it mockable; **single-tenant** owner gate (`authorize_owner_sub`: claim-on-first, then require-match → 403 for any other identity). `--oidc-issuer`/`--oidc-audience` enable it (open when unset, for local dev). Tested fully offline: stub-verifier middleware tests + an adversarial battery against an in-process mock IdP (expired/wrong-iss/wrong-aud/unknown-kid/tampered/alg-confusion/missing-sub all rejected).
|
||||
- ✅ **Auth — client side (§13, slice 10b):** OAuth2 **device-code flow** (`hephd::oauth::DeviceFlow`: discover → start → poll handling `authorization_pending`/`slow_down`, + refresh). `TokenStore` (OS keyring via `keyring`, in-memory for tests); `current_bearer` refreshes on expiry. `heph auth login` runs the flow + caches the token; spokes (`sync_once`) and `client` mode (`RemoteStore`) attach the bearer, refreshing as needed (`--oidc-issuer`/`--oidc-client-id`). **All auth/proxy HTTP uses `ureq`** (runtime-free blocking) — `reqwest::blocking` panics inside the daemon's `spawn_blocking`; async `reqwest` remains only for `sync_once`. Tested offline against a mock OAuth server (device flow, refresh, store) + a full spoke⇄authed-hub loop.
|
||||
- ✅ **CLI (§1):** `heph` next/task/doc/get/export/search/journal/**auth login·logout**.
|
||||
- ✅ **CLI (§1) — the complete daemon API + task driver:** `heph` covers every RPC (next/list/task/done/drop/skip/attention/**edit**/promote/show/log/health/doc/node/get/resolve/search/journal/links/backlinks/link/project[+list]/sync/conflicts/export/auth) with **human dates** (`--do-date tomorrow|+3d|fri|ISO`) and **recurrence** (presets + NL + raw `--rrule`; `datespec`). Reschedule is the new **`task.set_schedule`** RPC. Process-tested over a real socket.
|
||||
- ✅ **Daemon as an OS service (§3, [[design]] §4):** **`heph daemon start/stop/restart/status/uninstall`** idempotently manages a launchd agent (macOS) / systemd user service (Linux) running `hephd` on the default store; render fns unit-tested, verified live on macOS. The default socket is now a **stable** `<data-dir>/heph/hephd.sock` (was `$TMPDIR`-based) so a persistent service and its clients always agree. Surfaces are **connect-only** (no auto-spawn).
|
||||
- ✅ **Todoist importer (tooling):** `mise run import-todoist` seeds a store from Todoist (dry-run default, `-- --commit` to write) — see [[import-todoist]].
|
||||
- ✅ **CI (§9):** Forgejo `build.yaml` runs **entirely through Dagger** (the k8s job image is a thin Alpine + Dagger orchestrator with a DinD sidecar — no native Rust/nvim toolchain): `dagger call check` (cargo fmt/clippy/test on `rust:1-bookworm`) + `dagger call test-nvim` (build hephd + headless e2e). Cargo parallelism capped (`CARGO_BUILD_JOBS`) to avoid OOMing the build engine; cargo caches shared across runs. `prek` runs locally via git hooks, not in CI.
|
||||
- ✅ **`heph.nvim` slice 11a (§8) — the primary surface begins:** the Lua plugin (`heph.nvim/`) as a thin client of the `hephd` unix socket. **RPC client** over a `vim.uv` pipe (blocking `call` via `vim.wait`; id-demuxed; partial-line buffered; `luanil` so JSON `null`→`nil`; isolated `Session`s for tests). **Buffer-backed nodes** — `heph://node/<id>` buffers (`buftype=acwrite`), `BufReadCmd`→`node.get` / `BufWriteCmd`→`node.update` (whole-buffer body, CRDT-diffed; exact round-trip). **`[[wiki-link]]` follow** on `<CR>` via a new **`node.resolve {title}`** RPC (exact alias-then-title match, the same mapping that materializes `wiki` links — never fuzzy `search`; unresolved links allowed). **Daily journal** (`:Heph today`/`journal <date>`, idempotent). `:Heph` command surface + completion. **Headless e2e (§9):** drives the plugin in `nvim --headless` against a real daemon over a temp socket via a **self-contained busted-style runner** (`tests/e2e/runner.lua` — no external plugins/network, deterministic exit codes); specs cover journal round-trip, follow-link (+ unresolved no-op), and link-two-docs/backlink. `mise run test-nvim` builds the daemon and runs the suite (dev: system nvim/rustc; CI: a Dagger container provides them — slice 11c).
|
||||
- ✅ **`heph.nvim` slice 11b (§8) — task views:** **`list` enriched** to titled [`RankedTask`] rows (title + `canonical_context_id`, shared `ranked_from_row` with `next`) so the Organizational view needs no N+1 `node.get`. Plugin: **Tactical `next`** + **Organizational `list`** views (rendered scratch buffers, `<CR>` opens a row's canonical-context doc — the node autocmd narrowed to `heph://node/*` so view buffers don't trip it); **task capture**, **set-attention**, **done/drop**, **skip**, **per-task `log` append** — all resolving "the current task" from the buffer (a `task` node, or a context doc via its `canonical-context` backlink); **`vim.ui.select` picker** (`picker.lua`) with Telescope auto-upgrade; `:Heph next/list/capture/attention/done/drop/skip/log/search` subcommands. e2e specs: **capture→next→open context→add/check checklist→done**, and **recurring fresh-checklist** (complete rolls forward in place; the next occurrence is all-unchecked — the §4.4 hard requirement).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue