[2026-06-25] taiga-vault: family/how-to/htpc-emulators-setup.md family/how-to/htpc-gaming-plans.md family/how-to/kraken-access.md family/how-to/openmediavault-rpi5.md family/how-to/time-machine.md family/how-to/wireguard-vpn.md personal/documents/todo-list.md personal/plans/extract-stable-prompt-blocks.md personal/plans/hermes-whale-system-prompt.md personal/plans/thread-scoped-memory.md

This commit is contained in:
Taiga
2026-06-25 05:23:46 +00:00
parent ac0d753ec0
commit 16987d69f3
26 changed files with 2656 additions and 457 deletions
@@ -39,7 +39,7 @@ FastAPI + HTMX + Tailwind CDN + Alpine.js. No build step, CDN-only frontend.
├── services.yaml # Service definitions (source of truth)
├── .env # EAGLE_TOKEN=<secret> (not committed)
├── templates/
│ └── index.html # Dashboard UI — 3 tabs: Services, Pages, Files
│ └── index.html # Dashboard UI — 4 tabs: Services, Crons, Pages, Files
└── com.eagle.dashboard.plist
```
@@ -133,14 +133,102 @@ PID-file-based — survives Dashboard restarts without crashing.
---
## Tabs — заглушка урл
Каждый таб имеет свой URL через `?tab=` query-параметр. Переключение через Alpine.js `setUrlTab()`, который использует `history.replaceState()`. При загрузке `activeTab` инициализируется из `URLSearchParams` (с fallback на `'services'`). При клике на таб URL обновляется без перезагрузки страницы. Бэкенд также принимает `?tab=` в `GET /` и передаёт его как `initial_tab` в шаблон (пока не используется).
**Урлы:**
- `http://dashboard.qentra.top/` — Services
- `http://dashboard.qentra.top/?tab=crons` — Crons
- `http://dashboard.qentra.top/?tab=pages` — Pages
- `http://dashboard.qentra.top/?tab=files` — Files
## Tabs
**Services** — health cards with: status badge, PID, uptime, memory (RSS + children), start/stop/restart controls, log tail drawer (last 200 lines).
**Pages** — URL input + iframe for quick local page testing.
**Pages** — URL input + metadata table for `/Library/WebServer/Documents/*.html` test pages. Each row shows filename, title, purpose, contents, modified date, and opens the page in a new browser tab.
**Files** — FileBrowser iframe at `http://localhost:8181`.
**Crons** (2-я вкладка) — управление cron jobs из 3 источников:
- **Hermes Eagle** (22 jobs) — read/write напрямую в `~/.hermes/cron/jobs.json`
- **Hermes Whale** (0 jobs) — read/write в `~/.hermes/hermes-whale/cron/jobs.json`
- **Launchd (User)** — launchd plist-ы из `~/Library/LaunchAgents/` с `StartInterval` или `StartCalendarInterval` (редактируемые через launchctl + plistlib)
Для каждого крона на карточке: name, schedule, enabled/disabled, last run, prompt preview, chips (skills, toolsets, model, workdir). Кнопки:
- **+ Cron** — создать новый крон (выбор source: Hermes Eagle / Hermes Whale / Launchd (User)). При выборе Launchd поля Deliver, Prompt, Model/Provider, Skills/Toolsets динамически скрываются через `x-show="cronModal.source !== 'launchd'"`.
- ✎ — открыть модал редактирования всех полей (name, schedule, deliver, prompt, script, model, provider, skills, toolsets, workdir)
- ⏸/▶ — pause/resume (toggle enabled)
- 🗑 — удалить крон
|**Launchd create** — `POST /api/crons` с `source=launchd` генерирует plist. Принимает: `name` (→ Label, префикс `com.` если не указан), `schedule` (только `every Ns/m/h/d` → StartInterval в секундах), `script` (→ ProgramArguments, разбивка по пробелам), `workdir` (→ WorkingDirectory). Cron-выражения и ISO-даты для launchd **не поддерживаются** — валидатор на фронтенде их отклоняет с сообщением "Launchd: используйте every 30m / every 2h / every 1d". Поля prompt, deliver, skills, toolsets, model, provider игнорируются. Backend парсит `every N` + суффикс s/m/h/d → множитель 1/60/3600/86400.
**`_list_launchd_crons()`** теперь показывает **все** plist-ы из `~/Library/LaunchAgents/`, не только с StartInterval/StartCalendarInterval. Для plist без schedule — `schedule: "none (manual)"`. Это фикс: раньше plist без schedule (например созданный с cron-выражением) не отображался в дашборде.
**Schedule input — формат подсказки и валидация зависят от source:**
- Для **launchd**: подсказка только `every 30s / 30m / 2h / 1d — интервал`. Плейсхолдер `every 30m / every 2h`. Валидатор reject cron и ISO.
- Для **Eagle/Whale**: полная cron-схема (5 полей), пояснения `*`, `*/N`, `N-M`, `A,B`, `N`, плюс `every 30s / 30m / 2h / 1d` и ISO-дата.
**Schedule hint** — сворачиваемый multiline блок над полем ввода, открывается по кнопке `? подсказка`. Финальный рабочий текст:
```
┌── минута (0-59)
│ ┌── час (0-23)
│ │ ┌── день месяца (1-31)
│ │ │ ┌── месяц (1-12)
│ │ │ │ ┌── день недели (0-6, вс=0)
│ │ │ │ │
* * * * *
* — любое значение
*/N — с шагом N (*/30 = каждые 30)
N-M — диапазон (1-5 = пн-пт)
A,B — список (0,6 = вс,сб)
N — точное число (0 = в 0 мин)
every 30s / 30m / 2h / 1d — человеческий формат
2026-07-01T09:00 — ISO, одноразово (только Eagle/Whale)
```
- `scheduleHintOpen` — boolean в `cronModal`, по умолчанию `false`
- При открытии модала сбрасывается в `false`
- ISO-строка скрыта для launchd через `x-if="cronModal.source !== 'launchd'"`
**API эндпоинты:**
- `GET /` — главная страница, опционально `?tab=crons|pages|files|services`
- `GET /api/crons` — список всех кронов
- `POST /api/crons` — создать новый крон (body: source, name, schedule, prompt, script, deliver, skills, enabled_toolsets, model, provider, workdir). Для source=launchd: только name, schedule, script, workdir используются — генерируется plist и загружается через launchctl
- `PATCH /api/crons/{source}/{job_id}` — редактировать поля
- `POST /api/crons/{source}/{job_id}/toggle` — включить/выключить
- `DELETE /api/crons/{source}/{job_id}` — удалить
**Формат Hermes cron jobs.json:**
```json
{
"jobs": [
{
"id": "531b242c30ad",
"name": "data-pipeline",
"prompt": "[SILENT] bash ...",
"skills": [],
"skill": null,
"model": null,
"provider": null,
"script": null,
"schedule": { "kind": "cron", "expr": "*/30 7-21 * * 1-5", "display": "*/30 7-21 * * 1-5" },
"schedule_display": "*/30 7-21 * * 1-5",
"enabled": false,
"state": "paused",
"deliver": "local",
"enabled_toolsets": null,
"workdir": null
}
]
}
```
---
## Deployment
@@ -171,10 +259,32 @@ launchctl load ~/Library/LaunchAgents/com.eagle.dashboard.plist
**Ghost launchctl entries** — after removing plists, use `launchctl remove <label>` (not `bootout`) to clear bootstrap session entries.
**`_launchd_toggle()` known bug** — `_launchd_toggle()` in `main.py` uses `launchctl list <label>` exit code (0 = loaded) to determine "enabled". But `launchctl list` returns 0 even when process is **not running**, as long as plist is registered. After creating a launchd cron via API (`launchctl load -w`), plist is loaded → `enabled=true` → UI shows Pause. Clicking calls unload → `enabled=false` → "✓ paused". Functionally correct but button state appears inverted if user expected Resume. Fix: rewrite `_launchd_toggle()` to check `Disabled` key in plist after unload rather than `launchctl list` exit code.
## Troubleshooting — launchd cron doesn't run (EX_CONFIG)
When a launchd cron is created and `launchctl print gui/<uid>/<label>` shows `last exit code = 78: EX_CONFIG`:
1. **`~` not expanded in ProgramArguments.** launchd does not expand `~`. Use full path: `/Users/admin/scripts/foo.sh`
2. **Script itself exits 78.** Check script logic: `exit 78` is `EX_CONFIG` (configuration error). Fix the script.
## Troubleshooting — sync-vault appeared as "none (manual)"
**Root cause:** Launchd create accepted cron expression `*/5 * * * *` (pre-fix), couldn't parse it into StartInterval, wrote plist without schedule → `_list_launchd_crons()` (pre-fix) filtered it out. Fixed in two ways:
- Backend create now rejects non-`every` for launchd (frontend validator also blocks)
- `_list_launchd_crons()` now shows ALL plists (schedule: "none (manual)" if no StartInterval/StartCalendarInterval)
**Virfield port conflict** — if previously a LaunchAgent (plist deleted but still loaded), stop it: `launchctl stop com.virfield.server && launchctl remove com.virfield.server`.
---
## History
- 2026-06-04: Initial build. Migrated 14 LaunchAgent plist services under Dashboard supervisor. Single `com.eagle.dashboard` Login Item. cloudflared tunnel at `dashboard.qentra.top`. Ghost entries cleaned via `launchctl remove`.
- 2026-06-25 (round 8): **Multiple bugfixes.** `_msg` timer fix — stale object reference replaced with `find` in fresh array (both cron toggle and service action). `_list_launchd_crons()` enabled fix — removed `and pid is not None` (timer-based launchd jobs always had `enabled: false`). `openAddCron()` — source-dependent default schedule (`every 30m` for launchd, `*/30 * * * *` for Hermes), `scheduleErr` init via `validateSchedule()`. `@change` on source selector replaces default if switching hermes→launchd. Backend create for launchd: only `every Ns/m/h/d` parsed (cron-parser removed). `_list_launchd_crons()` now shows ALL plists.
- 2026-06-25 (round 7): **Launchd schedule — source-dependent validation.** `validateSchedule()` now takes `source` param. For `launchd`: only `every Ns/m/h/d` accepted; cron and ISO rejected with explicit message. For Eagle/Whale: all three formats. Hints, placeholders, and `prettySchedule()` also differ by source. `Query` import added to FastAPI.
- 2026-06-25 (round 6): **Tab URL routing.** Each tab now persists in URL via `?tab=` query-param + `history.replaceState()`. Frontend initializes `activeTab` from URL. Backend accepts `?tab=` on `GET /`. Added `setUrlTab()` to Alpine.js data.
- 2026-06-25 (round 4): **Schedule hint final format applied.** Multiline cron reference (no `#` prefix), field labels (m/h/d/m/w), value notation (`*/N`, `N-M`, `A,B`, `N`), human format (`every 30s / 30m / 2h / 1d`), ISO date. Collapsible via `? подсказка` button. ISO line hidden for launchd. Validator also blocks Save on invalid input.
- 2026-06-25 (round 3): **Schedule hint/validator polish.** Replaced `*/30 * * * *` with `every 30m` in hint text. Added `prettySchedule()`. Removed all prefatory labels from hint.
- 2026-06-25 (round 1): Added **launchd create** support — +Cron source selector now includes "⏰ Launchd (User)". Backend generates plist. Fields for Hermes-only dynamically hidden for launchd.
- 2026-06-24: Added **Crons** tab — management of Hermes Eagle/Whale cron jobs + Launchd (User) agents. Tab moved to 2nd position. `+Cron` button with source selector (Eagle/Whale). Full edit modal: name, schedule, prompt, script, deliver, skills, toolsets, model, provider, workdir. Toggle pause/resume, delete. All sections editable — launchd crons toggle via `launchctl unload/load -w`, edit via `plistlib`+reload, delete via unload+rm. API: `GET/POST/PATCH/DELETE /api/crons`. `json` and `datetime` imports added to main.py.
- 2026-06-04: Initial build.