sherpa-hub
Hub to track my app development
- Primary: https://hub.kenlill.com
- GitHub: https://github.com/klill6506/sherpa-hub
- Local:
D:\dev\sherpa-hub
Coolify
- App:
sherpa-hub - Status: live
- URL: https://kenlill.com
README
# sherpa-hub A read-only aggregator dashboard for Ken's app portfolio. - See `docs/superpowers/specs/2026-05-06-sherpa-hub-rebuild-design.md` for the design. - See `docs/superpowers/plans/2026-05-06-sherpa-hub-rebuild.md` for the implementation plan. - `web/` is the deployed FastAPI hub (read-only; reads `hub-cache.json`). - `sync/` is the local Windows sync script that builds the cache and POSTs it. - `legacy/` is the original sherpa-hub repo, kept as reference only (gitignored). ## Local development ### Hub (web) ```sh cd web poetry install npm install npm run build:css poetry run uvicorn app.main:app --reload ``` Open http://localhost:8000/healthz to confirm it's running. UI routes require `HUB_USERNAME`/`HUB_PASSWORD` env vars. ### Sync (local) ```sh cd sync poetry install cp .env.example .env # then fill in real values poetry run python -m sync ``` This pulls from GitHub + Render, builds `hub-cache.json`, and POSTs it to the deployed hub at `HUB_URL`. ## Tests ```sh cd web && poetry run pytest -v # 35 tests cd ../sync && poetry run pytest -v # 50 tests ```
STATUS
# sherpa-hub — Status
## Stale-app re-engagement nudge — SHIPPED (2026-06-17)
Weekly Slack nudge listing apps Ken hasn't committed to in 30+ days (excludes
archived/Retired/Parked/hidden), longest-neglected first, top 8. Entirely sync-side;
the cloud hub is untouched.
- Spec: `docs/superpowers/specs/2026-06-17-stale-app-nudge-design.md`
- Plan: `docs/superpowers/plans/2026-06-17-stale-app-nudge.md` (4 TDD tasks,
subagent-driven: implementer + opus spec/quality review → "ship it")
- Code: `sync/sync/nudge.py` (pure `select_neglected` + `format_slack_message`, plus
`main()` / `python -m sync.nudge`). **72 sync tests** (58 + 14 new).
- Ops: `sync/run-nudge.cmd` + weekly Task Scheduler entry **"Sherpa Hub Nudge"**
(Mon 08:00). Reads the hourly sync's `last-cache.json` + `overrides-backup.json`;
only new credential is `SLACK_WEBHOOK_URL` in `sync/.env`. See **D-019**.
- Verified live 2026-06-17: dry run exit 0, Slack 200, **7 apps flagged**, message in
`#hub-nudges`. Next scheduled run Mon 06/22 08:00.
- v2 (deferred): the "haven't *visited* it" traffic signal (needs analytics).
- Tunables (env, `sync/.env`): `NUDGE_THRESHOLD_DAYS` (30), `NUDGE_MAX_ITEMS` (8).
## Hub cleanup session — IN PROGRESS (2026-06-17)
Started as "add the running app," expanded into three threads. **103 web tests pass.**
**#1 Stale Render→Coolify links — fixed in `web/app/icons.yaml` (commit HELD).**
Root cause: the Coolify token was never in `sync/.env`, so every sync reported
"matched 0 Coolify apps" and migrated apps fell back to old (often suspended/dead)
Render URLs. The card link is `{{ ic.url or app.primary_url }}`, so a `url:`
override fixes it without a cache rebuild. Added/verified-live overrides:
`worth-the-run`→worth-the-run.kenlill.com, `whats-on`→whatson.kenlill.com,
`election-tracker`→election.kenlill.com, `sherpa-hub`→hub.kenlill.com,
`running-app`→running.kenlill.com (🏃 "Richmond · Ken's Training"; repo renamed
from `half-training-app`→`running-app`; icons.yaml key re
…(truncated for upload size)
DECISIONS
# sherpa-hub — Decisions > Architectural choices made during the rebuild brainstorm (2026-05-06). Not to be > re-litigated without explicit reason. ## D-001: Aggregator-only architecture (no DB, no owned data) **Date:** 2026-05-06 **Decision:** The hub stores no app metadata of its own. Every rendered field comes from GitHub, Render, a memory file, or the diary. The only thing the hub owns is a JSON cache of the last successful sync. **Why:** The legacy hub had its own copies of name/description/status/tags/priority/ notes/next_action — all of which already lived elsewhere. That's why it went stale. Removing ownership eliminates the failure mode entirely. ## D-002: Two-piece split — local sync + cloud hub **Date:** 2026-05-06 **Decision:** Local Windows script does all credentialed work (GitHub API, Render API, filesystem reads, diary reads). Builds `hub-cache.json`. POSTs it to the deployed hub. Hub on Render only reads the JSON. **Why:** Smaller attack surface (no API tokens on Render), simpler hub, naturally pairs with the SessionEnd diary write. Trade-off: requires Ken's PC to be running for fresh data — acceptable because he doesn't turn it off and refresh only matters when he's been coding. ## D-003: Stack — FastAPI + Jinja2 + Tailwind Plus + JSON cache + Poetry + Render **Date:** 2026-05-06 **Decision:** Same family as the legacy stack but cleaner. No SQLite, no DB at all. Server-rendered Jinja templates, not a SPA. **Why:** Data is small and derived. JSON cache is the right shape. Per global CLAUDE.md, "small tools don't need to follow the full suite stack" — this is firmly in that bucket. SPA is unnecessary complexity. ## D-004: Move diary from Obsidian/Dropbox to Google Drive plain Markdown **Date:** 2026-05-06 **Decision:** Diary lives at `G:\My Drive\kens-personal-life\diary\YYYY-MM-DD.md` with YAML front-matter (`projects: [slug, slug]`) plus per-project H2 sections in body. One file per coding day. Plain Markdown, no Obsidian dependency. **Why …(truncated for upload size)
MEMORY
# sherpa-hub — Memory ## Origin Ken had a previous sherpa-hub deployed on Render (suspended). It was a FastAPI + Jinja + SQLite app that hand-curated a registry of apps and a tasks list. The legacy DB had 12 apps and 0 tasks ever created — the registry went stale because it duplicated facts (name, description, status, priority, area, tags) that already lived in GitHub repos, STATUS.md files, and Render. The task feature was never adopted because Ken already tracks tasks in Claude Code, STATUS.md, and other tools. Legacy code is preserved at `legacy/` as reference (its own git history, gitignored). ## Why This Rebuild Looks Different The legacy was option-C in shape (registry + dashboard) but failed on input quality. The rebuild flips it: the hub stores nothing and aggregates from authoritative sources. Source-of-truth ownership: - **Repo identity / description / topics / archived** ← GitHub - **Deploy status / URL** ← Render API - **What's done / next / blocked** ← per-project STATUS.md - **Architecture / decisions / context** ← per-project DECISIONS.md, MEMORY.md, CLAUDE.md - **Daily activity & ideas** ← the diary at `G:\My Drive\kens-personal-life\diary\` ## Standing Facts - GitHub user: `klill6506` - Apps live in `D:\dev\` (work) and `D:\Personal\` (personal) - Memory files mirror to `G:\My Drive\kens-personal-life\apps\<project>\` via background sync - Diary is being moved from Obsidian/Dropbox to Google Drive plain Markdown as part of the same effort. Old location: `C:\Users\Ken2\Tax Shelter Dropbox\Ken Lill\KenVault\Claude Diary` - Diary structure (post-move): one file per day at `G:\My Drive\kens-personal-life\diary\YYYY-MM-DD.md` with YAML front-matter (`projects: [slug, slug]`) plus per-project H2 sections - Diary writing is automated via a SessionEnd hook that fires a headless Claude Code run to summarize the just-ended session and write the entry ## Deployment Learnings (2026-05-20) Stuff we figured out the hard way during the first real deploy …(truncated for upload size)
CLAUDE.md
# sherpa-hub — Project Conventions > Read-only aggregator dashboard for Ken's app portfolio. Pulls from GitHub, Render, > per-project memory files, and the daily diary. Owns no app data — every field is > rendered from a source of truth elsewhere. ## Architecture in One Paragraph Two pieces split by a JSON contract. `sync/` is a local Windows script that runs after every Claude Code session (via SessionEnd hook): it calls GitHub + Render APIs, reads local memory files from `D:\dev\` and `D:\Personal\`, reads the diary from `G:\My Drive\kens-personal-life\diary\`, and POSTs a `hub-cache.json` to the deployed hub. `web/` is a FastAPI + Jinja2 app on Render that reads that JSON and renders pages. The hub itself has no API credentials and no database. ## Stack - **Python:** 3.13 (per global CLAUDE.md, 3.12+ minimum) - **Backend:** FastAPI + Jinja2 + Tailwind Plus - **Cache:** JSON file on Render persistent disk — no database - **Packaging:** Poetry - **Hosting:** Render (Virginia), single web service for `web/` - **Auth:** HTTP Basic (single user) for the public UI; bearer token on `/admin/refresh` - **Sync runtime:** Python 3.13 on local Windows, invoked by Claude Code SessionEnd hook ## Directory Layout - `web/` — the deployed FastAPI hub (reads JSON, renders pages) - `sync/` — local Windows script (writes JSON, never deployed) - `docs/superpowers/specs/` — design specs (this brainstorm's output lives here) - `legacy/` — old FastAPI+SQLite hub, reference only, gitignored (separate repo) ## Hard Rules (Do Not Drift) - **The hub stores nothing about apps.** Every visible field is sourced from GitHub, Render, a memory file, or the diary. If a field needs to be added, find a source for it — do not introduce a "hub-owned" field. - **No database.** The whole point of v1 is no DB. JSON cache only. - **No credentials in `web/`.** Render env vars on the hub are limited to: `HUB_USERNAME`, `HUB_PASSWORD`, `HUB_UPLOAD_SECRET`. GitHub/Render/Google Drive tokens belon …(truncated for upload size)
Diary mentions
No recent diary mentions.