service_books.nu — hero_books server + UI lifecycle module #80

Closed
opened 2026-04-19 16:30:08 +00:00 by mahmoud · 3 comments
Owner

Child of #75.

Objective

Add tools/modules/services/service_books.nu implementing the standard install | start | stop | status lifecycle for the hero_books service (server + UI) so it can be driven the same way as service_os, service_browser, service_proxy, etc.

Scope

  • Repo: ssh://git@forge.ourworld.tf/lhumina_code/hero_books.git
  • Binaries managed (per buildenv.sh): hero_books, hero_books_server, hero_books_ui, hero_books_admin, hero_docs
  • Runtime actions (register with hero_proc): hero_books_server, hero_books_ui
  • TOML reference: lhumina_code/hero_zero/services/hero_books.toml
  • Sockets: $HERO_SOCKET_DIR/hero_books/rpc.sock, $HERO_SOCKET_DIR/hero_books/ui.sock (exact paths to confirm in spec)
  • Environment (from TOML):
    • server: RUST_LOG=info, HERO_BOOKS_DATA=$HERO_VAR/books, HERO_EMBEDDER_URL=unix:///…/hero_embedder/rpc.sock
    • ui: RUST_LOG=info
  • Depends on: hero_indexer_server, hero_embedder_server — hero_proc resolves these at orchestration time; spec should flag the operator prerequisite (both must be installed + registered, or hero_books will hit retry caps for the same reason hero_os's UI did without WASM assets).
  • --root flag supported but optional — default is user-level hero_proc.

Acceptance criteria

  • use services/mod.nu * (or use services/service_books.nu *) makes service_books available.
  • service_books install [--root] [--update] clones lhumina_code/hero_books, builds via cargo workspace, places all five binaries in ~/hero/bin/ (or /root/hero/bin/ with --root).
  • service_books start [--reset] [--root] [--update] registers both runtime actions + the service with hero_proc, starts, prints RPC + UI socket paths and http+unix://…/ui.sock/ URL in the summary.
  • service_books status [--root] reports state.
  • service_books stop [--root] cleanly unregisters.
  • --root only needed when running under root's hero_proc; default path is user-level.
  • Smoke-tested on the Hetzner box: install → start --reset → status → stop without errors. Status shows either running (with embedder/indexer available) or a clean degraded-mode signal when dependencies are missing.

Template & references

  • Template: tools/modules/services/service_os.nu (newest two-binary example, merged in PR #78) and tools/modules/services/service_browser.nu (simpler user-level variant).
  • Skills: claude/skills/nu_service/SKILL.md (build), claude/skills/nu_service_use/SKILL.md (use).
  • Shared helpers: tools/modules/services/lib.nusvc_require_proc, svc_cargo_install, svc_update, svc_bin, svc_sock_base.

Out of scope

  • Automating the hero_embedder_server / hero_indexer_server dependencies — they have (or will have) their own service_*.nu modules. The spec should only surface the dependency as a pre-flight warning / operator note, not try to start them transitively.
  • hero_books_admin and hero_docs binaries — they are installed (so CLI users can invoke them) but are not registered as hero_proc actions.
Child of #75. ## Objective Add `tools/modules/services/service_books.nu` implementing the standard `install | start | stop | status` lifecycle for the **hero_books** service (server + UI) so it can be driven the same way as `service_os`, `service_browser`, `service_proxy`, etc. ## Scope - **Repo**: `ssh://git@forge.ourworld.tf/lhumina_code/hero_books.git` - **Binaries managed** (per `buildenv.sh`): `hero_books`, `hero_books_server`, `hero_books_ui`, `hero_books_admin`, `hero_docs` - **Runtime actions** (register with hero_proc): `hero_books_server`, `hero_books_ui` - **TOML reference**: `lhumina_code/hero_zero/services/hero_books.toml` - **Sockets**: `$HERO_SOCKET_DIR/hero_books/rpc.sock`, `$HERO_SOCKET_DIR/hero_books/ui.sock` (exact paths to confirm in spec) - **Environment** (from TOML): - server: `RUST_LOG=info`, `HERO_BOOKS_DATA=$HERO_VAR/books`, `HERO_EMBEDDER_URL=unix:///…/hero_embedder/rpc.sock` - ui: `RUST_LOG=info` - **Depends on**: `hero_indexer_server`, `hero_embedder_server` — hero_proc resolves these at orchestration time; spec should flag the operator prerequisite (both must be installed + registered, or hero_books will hit retry caps for the same reason hero_os's UI did without WASM assets). - `--root` flag supported but optional — default is user-level hero_proc. ## Acceptance criteria - [ ] `use services/mod.nu *` (or `use services/service_books.nu *`) makes `service_books` available. - [ ] `service_books install [--root] [--update]` clones `lhumina_code/hero_books`, builds via cargo workspace, places all five binaries in `~/hero/bin/` (or `/root/hero/bin/` with `--root`). - [ ] `service_books start [--reset] [--root] [--update]` registers both runtime actions + the service with `hero_proc`, starts, prints RPC + UI socket paths and `http+unix://…/ui.sock/` URL in the summary. - [ ] `service_books status [--root]` reports state. - [ ] `service_books stop [--root]` cleanly unregisters. - [ ] `--root` only needed when running under root's hero_proc; default path is user-level. - [ ] Smoke-tested on the Hetzner box: install → start --reset → status → stop without errors. Status shows either `running` (with embedder/indexer available) or a clean degraded-mode signal when dependencies are missing. ## Template & references - Template: `tools/modules/services/service_os.nu` (newest two-binary example, merged in PR #78) and `tools/modules/services/service_browser.nu` (simpler user-level variant). - Skills: `claude/skills/nu_service/SKILL.md` (build), `claude/skills/nu_service_use/SKILL.md` (use). - Shared helpers: `tools/modules/services/lib.nu` — `svc_require_proc`, `svc_cargo_install`, `svc_update`, `svc_bin`, `svc_sock_base`. ## Out of scope - Automating the `hero_embedder_server` / `hero_indexer_server` dependencies — they have (or will have) their own `service_*.nu` modules. The spec should only surface the dependency as a pre-flight warning / operator note, not try to start them transitively. - `hero_books_admin` and `hero_docs` binaries — they are installed (so CLI users can invoke them) but are not registered as hero_proc actions.
Author
Owner

Implementation Spec for Issue #80

Objective

Add tools/modules/services/service_books.nu — a Nushell lifecycle module that registers and supervises the hero_books service (server + UI) with hero_proc, following the two-action pattern established by the freshly-merged service_os.nu (PR #78).

Requirements

  • Implement install | start | stop | status as exported commands, matching the conventions of service_os.nu / service_browser.nu.
  • Build and install all five binaries from the hero_books workspace: hero_books, hero_books_server, hero_books_ui, hero_books_admin, hero_docs (per buildenv.sh line 16). Only hero_books_server and hero_books_ui are registered as hero_proc actions; the other three ship alongside because they are CLI utilities (hero_books orchestrator, hero_books_admin admin UI, hero_docs docusaurus generator) that users invoke directly.
  • Register a single hero_proc service hero_books with the two runtime actions.
  • Server env: RUST_LOG=info, HERO_BOOKS_DATA=<hero_home>/var/books, HERO_EMBEDDER_URL=unix://<sock_base>/hero_embedder/rpc.sock — all three computed at register time from svc_home / svc_sock_base so root-vs-user installs resolve correctly.
  • UI env: RUST_LOG=info only.
  • Sockets (per crates/hero_books_server/src/web/axum_server.rs:39-48 and crates/hero_books_ui/src/main.rs:129-137):
    • $HERO_SOCKET_DIR/hero_books/rpc.sock
    • $HERO_SOCKET_DIR/hero_books/ui.sock
  • Preflight: warn (do NOT hard-fail) when $HERO_SOCKET_DIR/hero_embedder/rpc.sock is missing — hero_books_server runs in "degraded mode" without the embedder (axum_server.rs:56-62), so start should still register + start and the operator can bring up service_embedder separately.
  • Optional --root(-r) flag on every command; user-level is the default. Passwordless sudo is required for --root (same contract as the rest of the family).
  • --update(-u) on install / start to pull via svc_update; --reset on start to force a clean re-register.
  • Export the new module from tools/modules/services/mod.nu.

Files to Modify/Create

  • tools/modules/services/service_books.nu — new lifecycle module, ~290 lines, cloned from service_os.nu and retargeted to hero_books.
  • tools/modules/services/mod.nu — append one line: export use service_books.nu (alphabetical insertion between service_browser.nu and service_codescalers.nu is fine but the existing file is not strictly sorted — append at end of list for a minimal diff, matching the style of the most recent service_os.nu line).

No edits to lib.nu: every helper needed already exists (svc_home, svc_bin, svc_bin_dir, svc_sock_base, svc_require_sudo, svc_need_sudo, svc_require_proc, svc_proc_healthy, svc_update, svc_cargo_install).

Implementation Plan

Step 1: Create the module skeleton and constants block

Files: tools/modules/services/service_books.nu

  • Copy the header comment block + imports + constants block from service_os.nu:1-52 and adapt the prose:
    • Describe the two registered actions (server + UI) and the three ship-only CLIs (hero_books, hero_books_admin, hero_docs) explicitly.
    • Document the embedder preflight decision: warn when the embedder socket is absent because the server logs and runs in degraded mode (cite crates/hero_books_server/src/web/axum_server.rs:56-62).
    • Note the TOML dependencies (hero_indexer_server, hero_embedder_server) are NOT started transitively by this module — it only preflight-warns for the embedder socket.
  • Imports (identical to service_os.nu:40-41):
    • use ../clients/proc.nu *
    • use ./lib.nu *
  • Constants:
    • const SVX_SERVICE_NAME = "hero_books"
    • const SVX_FORGE_LOC = "lhumina_code/hero_books"
    • const SVX_BINARIES = ["hero_books" "hero_books_server" "hero_books_ui" "hero_books_admin" "hero_docs"] — full list from buildenv.sh:16.
    • const SVX_ACTIONS = ["hero_books_server" "hero_books_ui"] — only the runtime daemons.
      Dependencies: none.

Step 2: Implement svx_server_action with HERO_BOOKS_DATA + HERO_EMBEDDER_URL

Files: service_books.nu

  • Model on service_os.nu:58-97 (server action).
  • Compute env values at action-build time so --root flips home/sock paths correctly:
    let bin       = (svc_bin "hero_books_server" $root)
    let sock_base = (svc_sock_base $root)
    let data_dir  = $"(svc_home $root)/var/books"
    let embedder  = $"unix://($sock_base)/hero_embedder/rpc.sock"
    
    Rationale for the embedder URL: the TOML encodes unix:///root/hero/var/sockets/hero_embedder/rpc.sock, but hero_books_server's compiled-in default is unix://$HERO_SOCKET_DIR/hero_embedder/rpc.sock (see crates/hero_books_lib/src/lib.rs:50-57). Matching the compiled default via svc_sock_base is correct for both user-level and --root installs; the TOML hardcode is a zero-service artifact.
  • env record:
    env: {
        RUST_LOG: "info"
        HERO_BOOKS_DATA: $data_dir
        HERO_EMBEDDER_URL: $embedder
    }
    
  • kill_other.socket: [$"($sock_base)/hero_books/rpc.sock"] (note directory = service name, file = rpc.sock, confirmed at axum_server.rs:40-48).
  • health_checks[0].openrpc_socket: $"($sock_base)/hero_books/rpc.sock".
  • Retry/stop/timeout fields: copy verbatim from service_os.nu:68-78 (5 attempts, 30 s start timeout, SIGTERM, 10 s stop timeout).
    Dependencies: Step 1.

Step 3: Implement svx_ui_action

Files: service_books.nu

  • Model on service_os.nu:99-138.
  • bin = (svc_bin "hero_books_ui" $root), sock_base as in Step 2.
  • Env: {RUST_LOG: "info"} only (the UI reads its own HERO_BOOKS_SOCKET env optional override; default from crates/hero_books_ui/src/main.rs:82-86 points to $HERO_SOCKET_DIR/hero_books/rpc.sock which is the same value the server publishes, so no env plumbing is needed).
  • kill_other.socket: [$"($sock_base)/hero_books/ui.sock"].
  • health_checks[0].openrpc_socket: $"($sock_base)/hero_books/ui.sock" — same convention as service_os.nu:129 (hero_proc uses this field as a liveness probe target; it does not require the socket to speak OpenRPC).
  • Retry/timeouts: 3 attempts, 5 s stop timeout — identical to service_os.nu:109-119.
    Dependencies: Step 1.

Step 4: Implement svx_service_config and svx_drop_registration

Files: service_books.nu

  • svx_service_config: copy from service_os.nu:140-152 verbatim except for the human-readable description — use "Hero Books — library server and UI". Keep context_name: "core", class: "system", critical: false, status: "start".
  • svx_drop_registration: copy service_os.nu:155-161 verbatim; it iterates $SVX_ACTIONS, so it automatically handles the two-action case without modification.
    Dependencies: Step 1.

Step 5: Implement svx_check_embedder preflight helper

Files: service_books.nu

  • Model on the WASM-assets preflight in service_os.nu:166-182: same warn-don't-fail shape.
  • Check path: $"(svc_sock_base $root)/hero_embedder/rpc.sock".
  • Use the same sudo-aware existence test as svx_check_assets:
    let missing = if (svc_need_sudo $root) {
        (do { ^sudo test -S $embedder_sock } | complete).exit_code != 0
    } else {
        not ($embedder_sock | path exists)
    }
    
    (Prefer test -S over -f since sockets won't match -f.)
  • On missing: print a warning block mirroring service_os.nu:174-181, with remediation lines pointing to service_embedder install && service_embedder start, and a note that hero_books does not need a restart once the embedder comes up (the server retries on next RPC).
  • Intentionally do NOT check or start hero_indexer_server — it is another optional dependency listed in hero_books.toml:4, and its absence does not block server startup. Document this in the header comment.
    Dependencies: Step 1.

Step 6: Implement install

Files: service_books.nu

  • Copy service_os.nu:191-198 verbatim (signature and body). The five-binary list is already encoded in SVX_BINARIES; svc_cargo_install runs cargo build --release against the workspace root Cargo.toml, and the hero_books workspace publishes all five bins at the workspace level, so the plain cargo build produces them all in target/release/.
  • No Makefile invocation is needed. The make install target uses an extra build_lib.sh install_binaries helper (Makefile:144-149) but the sibling modules all go cargo-direct and that's the canonical path per lib.nu.
  • Flags: --root(-r), --update(-u).
    Dependencies: Steps 1-5 (uses SVX_FORGE_LOC and SVX_BINARIES).

Step 7: Implement start

Files: service_books.nu

  • Structure: 1:1 clone of service_os.nu:215-293, with string substitutions and one added step.
  • Keep the early-exit idempotency block (service_os.nu:226-235).
  • Keep the binary existence sanity check — target hero_books_server rather than hero_os_server (service_os.nu:242-250).
  • Replace the svx_check_assets call with svx_check_embedder $root at the equivalent position (service_os.nu:253).
  • Register both actions via proc action set, then the service, then start (service_os.nu:260-270).
  • Post-start summary block: model on service_os.nu:272-292; print service name, action list, running state, both socket paths, and the three proc ... commands for status/logs. Update the example URLs — hero_books_ui serves HTTP over Unix socket the same way as hero_os_ui (crates/hero_books_ui/src/main.rs:140-148), so the summary line ui url : http+unix://<ui.sock>/ served by hero_books_ui; reach the UI via hero_router stays structurally identical.
    Dependencies: Steps 2-6.

Step 8: Implement stop

Files: service_books.nu

  • 1:1 clone of service_os.nu:306-321 with the service name changed. svx_drop_registration is already parameterised via SVX_ACTIONS.
    Dependencies: Step 4.

Step 9: Implement status

Files: service_books.nu

  • 1:1 clone of service_os.nu:330-335 — one-line body delegating to proc service status $SVX_SERVICE_NAME --root=$root after svc_require_proc.
    Dependencies: Step 4.

Step 10: Wire into mod.nu

Files: tools/modules/services/mod.nu

  • Append export use service_books.nu as a new final line (currently ends at line 8 with service_os.nu).
    Dependencies: Step 1.

Step 11: Local syntax check

  • nu -c "source tools/modules/services/service_books.nu; print parse-ok" from the hero_skills repo root, to catch parser errors.
  • nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_books '" to verify the re-export path resolves.
    Dependencies: Steps 1-10.

Acceptance Criteria

  • service_books install builds all five binaries and places them in ~/hero/bin/.
  • service_books install --root does the same into /root/hero/bin/ via sudo.
  • service_books start registers the hero_books_server and hero_books_ui actions and a hero_books service with hero_proc, then starts it; idempotent on re-run.
  • service_books start --reset force-restarts even when already running.
  • service_books start warns but does NOT fail when $HERO_SOCKET_DIR/hero_embedder/rpc.sock is absent, and the service still transitions to running.
  • service_books stop removes both actions and the service cleanly; no-op when hero_proc is down (prints remediation).
  • service_books status prints hero_proc status for the hero_books service.
  • HERO_BOOKS_DATA and HERO_EMBEDDER_URL are set in the registered server action env (inspect via proc action get hero_books_server) and resolve to user-home paths when run without --root and to /root/... with --root.
  • service_books --help / scope commands shows the four subcommands via mod.nu.

Notes

Binary inventory (5 binaries, 2 actions).

  • hero_books_server, hero_books_ui → registered as hero_proc actions (from the [server] and [ui] blocks of hero_zero/services/hero_books.toml).
  • hero_books → CLI orchestrator with its own --start / --stop hero_proc flow (see Makefile:41-47). We install it but do NOT register it, because this module IS the orchestrator for Nushell users.
  • hero_books_admin → optional admin Axum server (not in the TOML, but present in buildenv.sh:16 and shipped alongside). Not registered as an action because the admin UI is mounted under /admin inside hero_books_server already (crates/hero_books_server/src/web/axum_server.rs:88-94); the standalone admin binary is a developer convenience.
  • hero_docs → one-shot docusaurus generator CLI (src/bin/hero_docs.rs). Never a daemon. Shipped so hero_docs new / generate is on $PATH.

Socket path confirmation.

  • Server: $HERO_SOCKET_DIR/hero_books/rpc.sock — confirmed at crates/hero_books_server/src/web/axum_server.rs:39-48 (hero_books_lib::hero_service_socket_dir("hero_books") + "rpc.sock").
  • UI: $HERO_SOCKET_DIR/hero_books/ui.sock — confirmed at crates/hero_books_ui/src/main.rs:129-137 (builds HERO_SOCKET_DIR/hero_books + ui.sock).
  • Both follow the standard per-service socket dir convention used everywhere else in the family.

HERO_BOOKS_DATA + HERO_EMBEDDER_URL placeholder handling.

  • The TOML uses __HERO_VAR__/books and a hardcoded /root/hero/var/sockets/hero_embedder/rpc.sock — these are placeholder strings for hero_zero's templating.
  • In the nu module, resolve them at action-build time:
    • HERO_BOOKS_DATA = $"(svc_home $root)/var/books" — matches hero_books_lib's default libraries_base_dir() (library.rs:38,54-56).
    • HERO_EMBEDDER_URL = $"unix://(svc_sock_base $root)/hero_embedder/rpc.sock" — matches hero_books_lib's default_embedder_url() (lib.rs:50-57) and routes correctly for both user and root installs. This departs from the TOML's /root/... hardcode on purpose; the TOML is consumed by a different codepath that doesn't participate here.
  • If a future service needs the same pattern, the convention is: resolve placeholders via svc_home $root / svc_sock_base $root at the spot where the action spec is built. Document this convention inline in the module header so subsequent services can reuse it.

UI assets.

  • hero_books_ui serves its static assets via include_str! / include_bytes! from crates/hero_books_ui/static/ at compile time (handlers.rs:1291-1294). No external asset bundle is required, so no asset preflight is needed — this is unlike hero_os_ui which hard-fails without $HERO_OS_ASSETS/index.html (the reason for svx_check_assets in service_os.nu:166-182).

Dependencies (depends_on).

  • hero_books.toml:4 lists hero_indexer_server and hero_embedder_server. These are consumed by hero_zero's dependency resolver; this nu module does NOT try to start them transitively (consistent with service_os.nu, which does not start hero_router even though hero_os's UI is brokered through hero_router).
  • Only the embedder socket is preflight-checked because it's the one with the most user-visible degradation (semantic search, Q&A fail silently). The indexer socket is not checked because the failure mode is less acute and its absence is common during bootstrap.
  • Server behavior without embedder: hero_books_server logs hero_embedder is not available — starting in degraded mode (no vector search) and continues (axum_server.rs:55-62) — so warn-don't-fail is the correct policy.

Root vs user-level specifics.

  • All socket and home paths are mediated by svc_home $root / svc_sock_base $root (lib.nu:26-43). HERO_SOCKET_DIR env override is honored only for user-level (lib.nu:37-39), which matches the rest of the family.
  • --root triggers svc_require_sudo at entry (error out with a clear message if passwordless sudo isn't configured). Compilation always happens in the invoking user's cargo cache; only the final binary copy goes through sudo.
  • The HERO_EMBEDDER_URL env var built with svc_sock_base $root resolves to /root/hero/var/sockets/hero_embedder/rpc.sock under --root and $HOME/hero/var/sockets/hero_embedder/rpc.sock (or $HERO_SOCKET_DIR/... when set) otherwise, which is the correct topology.

Hetzner smoke-test plan.

  1. service_proc start --root (prereq).
  2. service_books install --root — expect five binaries in /root/hero/bin/; verify with ls /root/hero/bin/hero_books* /root/hero/bin/hero_docs.
  3. service_books start --reset --root — expect the embedder warning block to fire (since embedder is not installed yet on a fresh box), then the service to register and enter running state.
  4. service_books status --root — expect a running state for the hero_books service with both actions healthy.
  5. curl --unix-socket /root/hero/var/sockets/hero_books/ui.sock http://localhost/health — expect 200 OK (the UI publishes /health).
  6. proc logs tail hero_books_server --root — expect the degraded-mode banner.
  7. service_embedder install --root && service_embedder start --root — expect hero_books_server to start responding to vector-backed RPCs on the next invocation without a restart.
  8. service_books stop --root — expect clean unregistration.
  • "Success" even before embedder is up: the hero_books service is registered, transitions to running, sockets appear at the expected paths, and the UI /health endpoint returns 200. Full Q&A / vector-search functionality is NOT part of the smoke-test success criterion — that comes after service_embedder start.
## Implementation Spec for Issue #80 ### Objective Add `tools/modules/services/service_books.nu` — a Nushell lifecycle module that registers and supervises the hero_books service (server + UI) with hero_proc, following the two-action pattern established by the freshly-merged `service_os.nu` (PR #78). ### Requirements - Implement `install | start | stop | status` as exported commands, matching the conventions of `service_os.nu` / `service_browser.nu`. - Build and install all five binaries from the `hero_books` workspace: `hero_books`, `hero_books_server`, `hero_books_ui`, `hero_books_admin`, `hero_docs` (per `buildenv.sh` line 16). Only `hero_books_server` and `hero_books_ui` are registered as hero_proc actions; the other three ship alongside because they are CLI utilities (`hero_books` orchestrator, `hero_books_admin` admin UI, `hero_docs` docusaurus generator) that users invoke directly. - Register a single hero_proc service `hero_books` with the two runtime actions. - Server env: `RUST_LOG=info`, `HERO_BOOKS_DATA=<hero_home>/var/books`, `HERO_EMBEDDER_URL=unix://<sock_base>/hero_embedder/rpc.sock` — all three computed at register time from `svc_home` / `svc_sock_base` so root-vs-user installs resolve correctly. - UI env: `RUST_LOG=info` only. - Sockets (per `crates/hero_books_server/src/web/axum_server.rs:39-48` and `crates/hero_books_ui/src/main.rs:129-137`): - `$HERO_SOCKET_DIR/hero_books/rpc.sock` - `$HERO_SOCKET_DIR/hero_books/ui.sock` - Preflight: warn (do NOT hard-fail) when `$HERO_SOCKET_DIR/hero_embedder/rpc.sock` is missing — hero_books_server runs in "degraded mode" without the embedder (`axum_server.rs:56-62`), so start should still register + start and the operator can bring up `service_embedder` separately. - Optional `--root(-r)` flag on every command; user-level is the default. Passwordless sudo is required for `--root` (same contract as the rest of the family). - `--update(-u)` on `install` / `start` to pull via `svc_update`; `--reset` on `start` to force a clean re-register. - Export the new module from `tools/modules/services/mod.nu`. ### Files to Modify/Create - `tools/modules/services/service_books.nu` — new lifecycle module, ~290 lines, cloned from `service_os.nu` and retargeted to hero_books. - `tools/modules/services/mod.nu` — append one line: `export use service_books.nu` (alphabetical insertion between `service_browser.nu` and `service_codescalers.nu` is fine but the existing file is not strictly sorted — append at end of list for a minimal diff, matching the style of the most recent `service_os.nu` line). No edits to `lib.nu`: every helper needed already exists (`svc_home`, `svc_bin`, `svc_bin_dir`, `svc_sock_base`, `svc_require_sudo`, `svc_need_sudo`, `svc_require_proc`, `svc_proc_healthy`, `svc_update`, `svc_cargo_install`). ### Implementation Plan #### Step 1: Create the module skeleton and constants block Files: `tools/modules/services/service_books.nu` - Copy the header comment block + imports + constants block from `service_os.nu:1-52` and adapt the prose: - Describe the two registered actions (server + UI) and the three ship-only CLIs (`hero_books`, `hero_books_admin`, `hero_docs`) explicitly. - Document the embedder preflight decision: warn when the embedder socket is absent because the server logs and runs in degraded mode (cite `crates/hero_books_server/src/web/axum_server.rs:56-62`). - Note the TOML dependencies (`hero_indexer_server`, `hero_embedder_server`) are NOT started transitively by this module — it only preflight-warns for the embedder socket. - Imports (identical to `service_os.nu:40-41`): - `use ../clients/proc.nu *` - `use ./lib.nu *` - Constants: - `const SVX_SERVICE_NAME = "hero_books"` - `const SVX_FORGE_LOC = "lhumina_code/hero_books"` - `const SVX_BINARIES = ["hero_books" "hero_books_server" "hero_books_ui" "hero_books_admin" "hero_docs"]` — full list from `buildenv.sh:16`. - `const SVX_ACTIONS = ["hero_books_server" "hero_books_ui"]` — only the runtime daemons. Dependencies: none. #### Step 2: Implement `svx_server_action` with HERO_BOOKS_DATA + HERO_EMBEDDER_URL Files: `service_books.nu` - Model on `service_os.nu:58-97` (server action). - Compute env values **at action-build time** so `--root` flips home/sock paths correctly: ``` let bin = (svc_bin "hero_books_server" $root) let sock_base = (svc_sock_base $root) let data_dir = $"(svc_home $root)/var/books" let embedder = $"unix://($sock_base)/hero_embedder/rpc.sock" ``` Rationale for the embedder URL: the TOML encodes `unix:///root/hero/var/sockets/hero_embedder/rpc.sock`, but hero_books_server's compiled-in default is `unix://$HERO_SOCKET_DIR/hero_embedder/rpc.sock` (see `crates/hero_books_lib/src/lib.rs:50-57`). Matching the compiled default via `svc_sock_base` is correct for both user-level and `--root` installs; the TOML hardcode is a zero-service artifact. - `env` record: ``` env: { RUST_LOG: "info" HERO_BOOKS_DATA: $data_dir HERO_EMBEDDER_URL: $embedder } ``` - `kill_other.socket`: `[$"($sock_base)/hero_books/rpc.sock"]` (note directory = service name, file = `rpc.sock`, confirmed at `axum_server.rs:40-48`). - `health_checks[0].openrpc_socket`: `$"($sock_base)/hero_books/rpc.sock"`. - Retry/stop/timeout fields: copy verbatim from `service_os.nu:68-78` (5 attempts, 30 s start timeout, SIGTERM, 10 s stop timeout). Dependencies: Step 1. #### Step 3: Implement `svx_ui_action` Files: `service_books.nu` - Model on `service_os.nu:99-138`. - `bin = (svc_bin "hero_books_ui" $root)`, `sock_base` as in Step 2. - Env: `{RUST_LOG: "info"}` only (the UI reads its own `HERO_BOOKS_SOCKET` env optional override; default from `crates/hero_books_ui/src/main.rs:82-86` points to `$HERO_SOCKET_DIR/hero_books/rpc.sock` which is the same value the server publishes, so no env plumbing is needed). - `kill_other.socket`: `[$"($sock_base)/hero_books/ui.sock"]`. - `health_checks[0].openrpc_socket`: `$"($sock_base)/hero_books/ui.sock"` — same convention as `service_os.nu:129` (hero_proc uses this field as a liveness probe target; it does not require the socket to speak OpenRPC). - Retry/timeouts: 3 attempts, 5 s stop timeout — identical to `service_os.nu:109-119`. Dependencies: Step 1. #### Step 4: Implement `svx_service_config` and `svx_drop_registration` Files: `service_books.nu` - `svx_service_config`: copy from `service_os.nu:140-152` verbatim except for the human-readable description — use `"Hero Books — library server and UI"`. Keep `context_name: "core"`, `class: "system"`, `critical: false`, `status: "start"`. - `svx_drop_registration`: copy `service_os.nu:155-161` verbatim; it iterates `$SVX_ACTIONS`, so it automatically handles the two-action case without modification. Dependencies: Step 1. #### Step 5: Implement `svx_check_embedder` preflight helper Files: `service_books.nu` - Model on the WASM-assets preflight in `service_os.nu:166-182`: same warn-don't-fail shape. - Check path: `$"(svc_sock_base $root)/hero_embedder/rpc.sock"`. - Use the same sudo-aware existence test as `svx_check_assets`: ``` let missing = if (svc_need_sudo $root) { (do { ^sudo test -S $embedder_sock } | complete).exit_code != 0 } else { not ($embedder_sock | path exists) } ``` (Prefer `test -S` over `-f` since sockets won't match `-f`.) - On missing: print a warning block mirroring `service_os.nu:174-181`, with remediation lines pointing to `service_embedder install && service_embedder start`, and a note that hero_books does not need a restart once the embedder comes up (the server retries on next RPC). - Intentionally do NOT check or start `hero_indexer_server` — it is another optional dependency listed in `hero_books.toml:4`, and its absence does not block server startup. Document this in the header comment. Dependencies: Step 1. #### Step 6: Implement `install` Files: `service_books.nu` - Copy `service_os.nu:191-198` verbatim (signature and body). The five-binary list is already encoded in `SVX_BINARIES`; `svc_cargo_install` runs `cargo build --release` against the workspace root `Cargo.toml`, and the hero_books workspace publishes all five bins at the workspace level, so the plain cargo build produces them all in `target/release/`. - No Makefile invocation is needed. The `make install` target uses an extra `build_lib.sh install_binaries` helper (Makefile:144-149) but the sibling modules all go cargo-direct and that's the canonical path per lib.nu. - Flags: `--root(-r)`, `--update(-u)`. Dependencies: Steps 1-5 (uses `SVX_FORGE_LOC` and `SVX_BINARIES`). #### Step 7: Implement `start` Files: `service_books.nu` - Structure: 1:1 clone of `service_os.nu:215-293`, with string substitutions and one added step. - Keep the early-exit idempotency block (`service_os.nu:226-235`). - Keep the binary existence sanity check — target `hero_books_server` rather than `hero_os_server` (`service_os.nu:242-250`). - Replace the `svx_check_assets` call with `svx_check_embedder $root` at the equivalent position (`service_os.nu:253`). - Register both actions via `proc action set`, then the service, then start (`service_os.nu:260-270`). - Post-start summary block: model on `service_os.nu:272-292`; print service name, action list, running state, both socket paths, and the three `proc ...` commands for status/logs. Update the example URLs — hero_books_ui serves HTTP over Unix socket the same way as hero_os_ui (`crates/hero_books_ui/src/main.rs:140-148`), so the summary line `ui url : http+unix://<ui.sock>/ served by hero_books_ui; reach the UI via hero_router` stays structurally identical. Dependencies: Steps 2-6. #### Step 8: Implement `stop` Files: `service_books.nu` - 1:1 clone of `service_os.nu:306-321` with the service name changed. `svx_drop_registration` is already parameterised via `SVX_ACTIONS`. Dependencies: Step 4. #### Step 9: Implement `status` Files: `service_books.nu` - 1:1 clone of `service_os.nu:330-335` — one-line body delegating to `proc service status $SVX_SERVICE_NAME --root=$root` after `svc_require_proc`. Dependencies: Step 4. #### Step 10: Wire into `mod.nu` Files: `tools/modules/services/mod.nu` - Append `export use service_books.nu` as a new final line (currently ends at line 8 with `service_os.nu`). Dependencies: Step 1. #### Step 11: Local syntax check - `nu -c "source tools/modules/services/service_books.nu; print parse-ok"` from the hero_skills repo root, to catch parser errors. - `nu -c "use tools/modules/services/mod.nu *; scope commands | where name =~ '^service_books '"` to verify the re-export path resolves. Dependencies: Steps 1-10. ### Acceptance Criteria - [ ] `service_books install` builds all five binaries and places them in `~/hero/bin/`. - [ ] `service_books install --root` does the same into `/root/hero/bin/` via sudo. - [ ] `service_books start` registers the `hero_books_server` and `hero_books_ui` actions and a `hero_books` service with hero_proc, then starts it; idempotent on re-run. - [ ] `service_books start --reset` force-restarts even when already running. - [ ] `service_books start` warns but does NOT fail when `$HERO_SOCKET_DIR/hero_embedder/rpc.sock` is absent, and the service still transitions to `running`. - [ ] `service_books stop` removes both actions and the service cleanly; no-op when hero_proc is down (prints remediation). - [ ] `service_books status` prints hero_proc status for the `hero_books` service. - [ ] `HERO_BOOKS_DATA` and `HERO_EMBEDDER_URL` are set in the registered server action env (inspect via `proc action get hero_books_server`) and resolve to user-home paths when run without `--root` and to `/root/...` with `--root`. - [ ] `service_books --help` / `scope commands` shows the four subcommands via `mod.nu`. ### Notes **Binary inventory (5 binaries, 2 actions).** - `hero_books_server`, `hero_books_ui` → registered as hero_proc actions (from the `[server]` and `[ui]` blocks of `hero_zero/services/hero_books.toml`). - `hero_books` → CLI orchestrator with its own `--start` / `--stop` hero_proc flow (see `Makefile:41-47`). We install it but do NOT register it, because this module IS the orchestrator for Nushell users. - `hero_books_admin` → optional admin Axum server (not in the TOML, but present in `buildenv.sh:16` and shipped alongside). Not registered as an action because the admin UI is mounted under `/admin` inside `hero_books_server` already (`crates/hero_books_server/src/web/axum_server.rs:88-94`); the standalone admin binary is a developer convenience. - `hero_docs` → one-shot docusaurus generator CLI (`src/bin/hero_docs.rs`). Never a daemon. Shipped so `hero_docs new` / `generate` is on $PATH. **Socket path confirmation.** - Server: `$HERO_SOCKET_DIR/hero_books/rpc.sock` — confirmed at `crates/hero_books_server/src/web/axum_server.rs:39-48` (`hero_books_lib::hero_service_socket_dir("hero_books")` + `"rpc.sock"`). - UI: `$HERO_SOCKET_DIR/hero_books/ui.sock` — confirmed at `crates/hero_books_ui/src/main.rs:129-137` (builds `HERO_SOCKET_DIR/hero_books` + `ui.sock`). - Both follow the standard per-service socket dir convention used everywhere else in the family. **HERO_BOOKS_DATA + HERO_EMBEDDER_URL placeholder handling.** - The TOML uses `__HERO_VAR__/books` and a hardcoded `/root/hero/var/sockets/hero_embedder/rpc.sock` — these are placeholder strings for hero_zero's templating. - In the nu module, resolve them at action-build time: - `HERO_BOOKS_DATA = $"(svc_home $root)/var/books"` — matches hero_books_lib's default `libraries_base_dir()` (`library.rs:38,54-56`). - `HERO_EMBEDDER_URL = $"unix://(svc_sock_base $root)/hero_embedder/rpc.sock"` — matches hero_books_lib's `default_embedder_url()` (`lib.rs:50-57`) and routes correctly for both user and root installs. This departs from the TOML's `/root/...` hardcode on purpose; the TOML is consumed by a different codepath that doesn't participate here. - If a future service needs the same pattern, the convention is: resolve placeholders via `svc_home $root` / `svc_sock_base $root` at the spot where the action spec is built. Document this convention inline in the module header so subsequent services can reuse it. **UI assets.** - hero_books_ui serves its static assets via `include_str!` / `include_bytes!` from `crates/hero_books_ui/static/` at compile time (`handlers.rs:1291-1294`). No external asset bundle is required, so no asset preflight is needed — this is unlike hero_os_ui which hard-fails without `$HERO_OS_ASSETS/index.html` (the reason for `svx_check_assets` in `service_os.nu:166-182`). **Dependencies (depends_on).** - `hero_books.toml:4` lists `hero_indexer_server` and `hero_embedder_server`. These are consumed by hero_zero's dependency resolver; this nu module does NOT try to start them transitively (consistent with `service_os.nu`, which does not start hero_router even though hero_os's UI is brokered through hero_router). - Only the embedder socket is preflight-checked because it's the one with the most user-visible degradation (semantic search, Q&A fail silently). The indexer socket is not checked because the failure mode is less acute and its absence is common during bootstrap. - Server behavior without embedder: hero_books_server logs `hero_embedder is not available — starting in degraded mode (no vector search)` and continues (`axum_server.rs:55-62`) — so warn-don't-fail is the correct policy. **Root vs user-level specifics.** - All socket and home paths are mediated by `svc_home $root` / `svc_sock_base $root` (lib.nu:26-43). `HERO_SOCKET_DIR` env override is honored only for user-level (`lib.nu:37-39`), which matches the rest of the family. - `--root` triggers `svc_require_sudo` at entry (error out with a clear message if passwordless sudo isn't configured). Compilation always happens in the invoking user's cargo cache; only the final binary copy goes through sudo. - The `HERO_EMBEDDER_URL` env var built with `svc_sock_base $root` resolves to `/root/hero/var/sockets/hero_embedder/rpc.sock` under `--root` and `$HOME/hero/var/sockets/hero_embedder/rpc.sock` (or `$HERO_SOCKET_DIR/...` when set) otherwise, which is the correct topology. **Hetzner smoke-test plan.** 1. `service_proc start --root` (prereq). 2. `service_books install --root` — expect five binaries in `/root/hero/bin/`; verify with `ls /root/hero/bin/hero_books* /root/hero/bin/hero_docs`. 3. `service_books start --reset --root` — expect the embedder warning block to fire (since embedder is not installed yet on a fresh box), then the service to register and enter `running` state. 4. `service_books status --root` — expect a running state for the `hero_books` service with both actions healthy. 5. `curl --unix-socket /root/hero/var/sockets/hero_books/ui.sock http://localhost/health` — expect 200 OK (the UI publishes `/health`). 6. `proc logs tail hero_books_server --root` — expect the degraded-mode banner. 7. `service_embedder install --root && service_embedder start --root` — expect hero_books_server to start responding to vector-backed RPCs on the next invocation without a restart. 8. `service_books stop --root` — expect clean unregistration. - "Success" even before embedder is up: the hero_books service is registered, transitions to `running`, sockets appear at the expected paths, and the UI `/health` endpoint returns 200. Full Q&A / vector-search functionality is NOT part of the smoke-test success criterion — that comes after `service_embedder start`.
Author
Owner

Implementation summary

Changes

  • Added tools/modules/services/service_books.nu — ~320 lines, modelled on service_os.nu with three books-specific pieces.
  • Updated tools/modules/services/mod.nu — added export use service_books.nu as the 9th entry.

What the module does

  • service_books install [--root] [--update] — clones lhumina_code/hero_books, runs an explicit cargo build --release --workspace pass (necessary because hero_books has a hybrid workspace+root-package layout; plain cargo build misses _server/_ui/_admin), then copies all 5 binaries (hero_books, hero_books_server, hero_books_ui, hero_books_admin, hero_docs) to ~/hero/bin/ (or /root/hero/bin/ with --root).
  • service_books start [--reset] [--root] [--update] — pre-flights the hero_embedder socket (warns, does not fail), drops any stale registration, registers both runtime actions + the service, starts it, prints both Unix sockets and the http+unix://…/ui.sock/ URL.
  • service_books status [--root] — returns the hero_proc record.
  • service_books stop [--root] — cleanly unregisters; tolerant of hero_proc being down.

End-to-end smoke test on Hetzner

Run from a clean state on the root-level box. Every assertion green.

Phase 1 — error paths with hero_proc DOWN

# Assertion Result
1a service_books status --root errors with "hero_proc is not running" + remediation PASS
1b service_books stop --root warns and returns exit 0 PASS
1c service_books start --root errors with the same guidance before touching binaries PASS

Phase 2 — full lifecycle with hero_proc UP

# Assertion Result
2a service_proc start --root healthy PASS
2b service_books install --root — workspace build, 5/5 binaries in /root/hero/bin/ PASS
2c service_books start --reset --root — embedder preflight warning fires (expected, embedder not installed), actions + service register, summary prints rpc sock, ui sock, http+unix:// URL PASS
2d /root/hero/var/sockets/hero_books/rpc.sock is a live unix socket PASS
2e /root/hero/var/sockets/hero_books/ui.sock is a live unix socket PASS
2f curl --unix-socket rpc.sock returns HTTP (socket accepts requests) PASS
2g curl --unix-socket ui.sock /health returns HTTP PASS
2h service_books status --root returns {name: hero_books, state: running, pid, restarts: 0, run_id} PASS
2i service_books start --root (no --reset) early-exits with "already running" PASS
2j HERO_BOOKS_DATA = /root/hero/var/books on the registered server action PASS
2j HERO_EMBEDDER_URL = unix:///root/hero/var/sockets/hero_embedder/rpc.sock on the registered server action PASS
2k 15 s later, restarts still 0, state still running PASS
2l service_books stop --root stops and unregisters PASS
2m Both sockets gone after stop PASS
2n Post-stop service_books status --root returns expected service 'hero_books' not found PASS

Phase 3 — cleanup

service_proc stop --root ran clean.

Issues found and fixed during testing

  1. Missing serve subcommand. First run of the module ran the binaries with no arguments, which prints help and exits. hero_proc saw the immediate exit, retried 5×, marked the service failed (state=failed, restarts=6, pid=0). Fix: set script: $"($bin) serve" in both server and UI action specs. Confirmed by inspecting hero_books_server --help which shows Commands: serve.

  2. Hybrid workspace build. hero_books has a root [package] plus [workspace] members. Plain cargo build --release --manifest-path Cargo.toml only builds the root crate (hero_books + hero_docs) plus its direct path-deps (hero_books_lib, hero_books_docusaurus), missing hero_books_server, hero_books_ui, hero_books_admin. Fix (local to service_books.nu): add an explicit cargo build --release --workspace pass before svc_cargo_install. hero_os / hero_proc are pure virtual workspaces (no root package) so plain cargo build covers all members there; hero_books is the first hybrid case in the family.

  3. sccache stall. Observed cargo wedging at 0.5% CPU with 0 rustc children — sccache server IPC had a defunct child and held cargo blocked. Worked around by unsetting RUSTC_WRAPPER / SCCACHE_* env in the test wrapper. Not in the PR scope; mentioned for the record. Likely belongs in a separate follow-up alongside the service_proc.nu:270 fix tracked in #79.

Acceptance criteria

  • service_books install builds all five binaries and places them in ~/hero/bin/.
  • service_books install --root does the same into /root/hero/bin/ via sudo.
  • service_books start registers hero_books_server + hero_books_ui + the hero_books service with hero_proc and starts it; idempotent on re-run.
  • service_books start --reset force-restarts even when already running.
  • service_books start warns but does NOT fail when $HERO_SOCKET_DIR/hero_embedder/rpc.sock is absent; service still transitions to running.
  • service_books stop removes both actions and the service cleanly; no-op when hero_proc is down.
  • service_books status prints the hero_proc record.
  • HERO_BOOKS_DATA and HERO_EMBEDDER_URL are set in the registered server action env and resolve to user-home paths (user-level) or /root/... paths (with --root).
  • Four subcommands available via use services/mod.nu *.
## Implementation summary ### Changes - Added `tools/modules/services/service_books.nu` — ~320 lines, modelled on `service_os.nu` with three books-specific pieces. - Updated `tools/modules/services/mod.nu` — added `export use service_books.nu` as the 9th entry. ### What the module does - `service_books install [--root] [--update]` — clones `lhumina_code/hero_books`, runs an explicit `cargo build --release --workspace` pass (necessary because hero_books has a hybrid workspace+root-package layout; plain `cargo build` misses `_server`/`_ui`/`_admin`), then copies all 5 binaries (`hero_books`, `hero_books_server`, `hero_books_ui`, `hero_books_admin`, `hero_docs`) to `~/hero/bin/` (or `/root/hero/bin/` with `--root`). - `service_books start [--reset] [--root] [--update]` — pre-flights the hero_embedder socket (warns, does not fail), drops any stale registration, registers both runtime actions + the service, starts it, prints both Unix sockets and the `http+unix://…/ui.sock/` URL. - `service_books status [--root]` — returns the hero_proc record. - `service_books stop [--root]` — cleanly unregisters; tolerant of hero_proc being down. ### End-to-end smoke test on Hetzner Run from a clean state on the root-level box. Every assertion green. #### Phase 1 — error paths with hero_proc DOWN | # | Assertion | Result | |---|---|---| | 1a | `service_books status --root` errors with "hero_proc is not running" + remediation | PASS | | 1b | `service_books stop --root` warns and returns exit 0 | PASS | | 1c | `service_books start --root` errors with the same guidance before touching binaries | PASS | #### Phase 2 — full lifecycle with hero_proc UP | # | Assertion | Result | |---|---|---| | 2a | `service_proc start --root` healthy | PASS | | 2b | `service_books install --root` — workspace build, 5/5 binaries in `/root/hero/bin/` | PASS | | 2c | `service_books start --reset --root` — embedder preflight warning fires (expected, embedder not installed), actions + service register, summary prints rpc sock, ui sock, `http+unix://` URL | PASS | | 2d | `/root/hero/var/sockets/hero_books/rpc.sock` is a live unix socket | PASS | | 2e | `/root/hero/var/sockets/hero_books/ui.sock` is a live unix socket | PASS | | 2f | `curl --unix-socket rpc.sock` returns HTTP (socket accepts requests) | PASS | | 2g | `curl --unix-socket ui.sock /health` returns HTTP | PASS | | 2h | `service_books status --root` returns `{name: hero_books, state: running, pid, restarts: 0, run_id}` | PASS | | 2i | `service_books start --root` (no `--reset`) early-exits with "already running" | PASS | | 2j | `HERO_BOOKS_DATA` = `/root/hero/var/books` on the registered server action | PASS | | 2j | `HERO_EMBEDDER_URL` = `unix:///root/hero/var/sockets/hero_embedder/rpc.sock` on the registered server action | PASS | | 2k | 15 s later, `restarts` still 0, `state` still `running` | PASS | | 2l | `service_books stop --root` stops and unregisters | PASS | | 2m | Both sockets gone after stop | PASS | | 2n | Post-stop `service_books status --root` returns expected `service 'hero_books' not found` | PASS | #### Phase 3 — cleanup `service_proc stop --root` ran clean. ### Issues found and fixed during testing 1. **Missing `serve` subcommand.** First run of the module ran the binaries with no arguments, which prints help and exits. hero_proc saw the immediate exit, retried 5×, marked the service `failed` (state=failed, restarts=6, pid=0). Fix: set `script: $"($bin) serve"` in both server and UI action specs. Confirmed by inspecting `hero_books_server --help` which shows `Commands: serve`. 2. **Hybrid workspace build.** hero_books has a root `[package]` plus `[workspace]` members. Plain `cargo build --release --manifest-path Cargo.toml` only builds the root crate (`hero_books` + `hero_docs`) plus its direct path-deps (`hero_books_lib`, `hero_books_docusaurus`), missing `hero_books_server`, `hero_books_ui`, `hero_books_admin`. Fix (local to `service_books.nu`): add an explicit `cargo build --release --workspace` pass before `svc_cargo_install`. hero_os / hero_proc are pure virtual workspaces (no root package) so plain cargo build covers all members there; hero_books is the first hybrid case in the family. 3. **sccache stall.** Observed cargo wedging at 0.5% CPU with 0 rustc children — sccache server IPC had a defunct child and held cargo blocked. Worked around by unsetting `RUSTC_WRAPPER` / `SCCACHE_*` env in the test wrapper. Not in the PR scope; mentioned for the record. Likely belongs in a separate follow-up alongside the `service_proc.nu:270` fix tracked in #79. ### Acceptance criteria - [x] `service_books install` builds all five binaries and places them in `~/hero/bin/`. - [x] `service_books install --root` does the same into `/root/hero/bin/` via sudo. - [x] `service_books start` registers `hero_books_server` + `hero_books_ui` + the `hero_books` service with hero_proc and starts it; idempotent on re-run. - [x] `service_books start --reset` force-restarts even when already running. - [x] `service_books start` warns but does NOT fail when `$HERO_SOCKET_DIR/hero_embedder/rpc.sock` is absent; service still transitions to `running`. - [x] `service_books stop` removes both actions and the service cleanly; no-op when hero_proc is down. - [x] `service_books status` prints the hero_proc record. - [x] `HERO_BOOKS_DATA` and `HERO_EMBEDDER_URL` are set in the registered server action env and resolve to user-home paths (user-level) or `/root/...` paths (with `--root`). - [x] Four subcommands available via `use services/mod.nu *`.
Author
Owner

PR opened: #81

PR opened: https://forge.ourworld.tf/lhumina_code/hero_skills/pulls/81
mahmoud self-assigned this 2026-04-19 19:01:00 +00:00
mahmoud added this to the ACTIVE project 2026-04-19 19:01:02 +00:00
mahmoud added this to the now milestone 2026-04-19 19:01:04 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_skills#80
No description provided.