refactor(skills): live-dashboard becomes an optional skill reconfigured for the Desktop app
Why: the web dashboard is being deprecated in favour of the Electron Desktop app, and a skill that ships a bespoke cron blueprint should not be bundled by default. Reconfigure instead of just rebasing: - Move skills/productivity/live-dashboard -> optional-skills/productivity/ live-dashboard (install with `hermes skills install official/productivity/live-dashboard`); register it like every other optional skill: per-skill docs page under user-guide/skills/optional/, optional-skills-catalog row, sidebars entry. - Desktop reality: add a "Show the dashboard" step — when `desktop_preview` is in the toolset (Desktop/GUI sessions) render index.html in the in-app preview pane after every build/tick and on request; otherwise report the absolute file path. Prerequisites section added (Enough1122 review). - Drop the hard-wired cron/blueprint_catalog.py entry: the curated catalog is for bundled skills and would preload a skill that may not be installed. Use the skills-pipeline blueprint instead — `metadata.hermes.blueprint` on the SKILL.md registers a daily all-dashboards sweep as a /suggestions entry at install time (opt-in, never auto-scheduled), which is exactly the mechanism main provides for optional skills. - Never hardcode ~/.hermes in prose the agent executes: refer to the Hermes home directory's dashboards/<slug>/ and write absolute paths into cron prompts (also answers the review's "tick prompt must name the state-file path" point). - Tests follow the skill to optional-skills/, the catalog-blueprint tests are replaced by one parse_blueprint/blueprint_to_job_spec invariant and one desktop_preview-with-path-fallback invariant.
This commit is contained in:
@@ -329,52 +329,6 @@ CATALOG: List[AutomationBlueprint] = [
|
||||
skills=("product-price-monitor",),
|
||||
tags=("prices", "shopping", "travel", "monitor"),
|
||||
),
|
||||
AutomationBlueprint(
|
||||
# Inspired by Energy's (getenergy.com) one-sentence live dashboards:
|
||||
# describe what you want to see, the agent builds a persistent view
|
||||
# and keeps it current from email/web/file sources.
|
||||
key="live-dashboard",
|
||||
title="Live status dashboard",
|
||||
description="A self-updating dashboard for any project or process — "
|
||||
"describe what to track and it refreshes from your email, sites, "
|
||||
"and files.",
|
||||
category="general",
|
||||
schedule_template="{minute} {hour} * * {dow}",
|
||||
prompt_template=(
|
||||
"Load the live-dashboard skill and run the refresh tick for this "
|
||||
"dashboard: {purpose}. Sources: {sources}. Re-read each field from "
|
||||
"its named source, diff against the stored state, mark failed "
|
||||
"reads stale without overwriting last-known-good values, "
|
||||
"regenerate the HTML from the state file, and deliver a short "
|
||||
"summary ONLY on material change or new needs-attention items — "
|
||||
"otherwise respond with [SILENT]. On the first run, execute the "
|
||||
"skill's setup phase first: pin the dashboard contract, verify "
|
||||
"one live read per source, and write the dashboard state file "
|
||||
"and HTML under ~/.hermes/dashboards/."
|
||||
),
|
||||
slots=[
|
||||
BlueprintSlot(
|
||||
name="purpose", type="text", label="What should it track?",
|
||||
default="the status of an ongoing project or process",
|
||||
help="one sentence — the entities and what you want to see "
|
||||
"about them",
|
||||
),
|
||||
BlueprintSlot(
|
||||
name="sources", type="text", label="Where does the data live?",
|
||||
default="my email threads about it and the relevant website",
|
||||
help="email, websites, calendars, or files to read each refresh",
|
||||
),
|
||||
_TIME("08:00"),
|
||||
BlueprintSlot(
|
||||
name="recurrence", type="weekdays", label="Refresh on",
|
||||
default="everyday",
|
||||
options=tuple(WEEKDAY_PRESETS.keys()),
|
||||
),
|
||||
_DELIVER,
|
||||
],
|
||||
skills=("live-dashboard",),
|
||||
tags=("dashboard", "monitor", "status"),
|
||||
),
|
||||
AutomationBlueprint(
|
||||
key="competitor-watch",
|
||||
title="Competitor news watch",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: live-dashboard
|
||||
description: "Build self-updating dashboards from live sources."
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
@@ -9,23 +9,33 @@ metadata:
|
||||
hermes:
|
||||
tags: [Dashboards, Monitoring, Status, Automation, Reporting]
|
||||
related_skills: [product-price-monitor, competitor-news-monitor, email-inbox-triage, google-workspace]
|
||||
blueprint:
|
||||
schedule: "0 8 * * *"
|
||||
prompt: "Load the live-dashboard skill and run the refresh tick for every dashboard.json under the Hermes home directory's dashboards/ folder. Mark failed reads stale without overwriting last-known-good values, regenerate each index.html from its state file, and deliver a short summary ONLY on material change or new needs-attention items — otherwise respond with [SILENT]."
|
||||
---
|
||||
|
||||
# Live Dashboard
|
||||
|
||||
Turn one sentence — "make a dashboard for our visa applications, update it daily from the email threads and the case-status site" — into a persistent, self-refreshing status page. The user describes what they want to see; you define the data contract, build a self-contained HTML dashboard, verify one live refresh, then schedule the recurring tick. Inspired by Energy's (getenergy.com) natural-language live dashboards, adapted to Hermes's cron + connector architecture.
|
||||
|
||||
Setup runs once in the foreground; the recurring refresh runs as a `cronjob` tick (the `live-dashboard` automation blueprint scaffolds this).
|
||||
Setup runs once in the foreground; the recurring refresh runs as a `cronjob` tick. Installing this skill offers a daily all-dashboards sweep via `/suggestions` (the frontmatter blueprint).
|
||||
|
||||
## When to Use
|
||||
|
||||
- "Make a dashboard for <project/process> and keep it updated."
|
||||
- "I want one place to see the status of <deals / applications / bugs / shipments>."
|
||||
- "Track <thing> across my email and <website> and show me where it stands."
|
||||
- "Show me the <slug> dashboard." (re-render/preview an existing one)
|
||||
- A cron tick fires for an existing dashboard (steps 5-7).
|
||||
|
||||
Don't use for: one-off status questions (answer directly), price/availability thresholds on a single item (use `product-price-monitor`), or company news tracking (use `competitor-news-monitor`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- At least one source connector the dashboard will read from: email/calendar via `himalaya` or `google-workspace`, websites via `web_extract` or `browser_navigate`, local files via `read_file`. If none is configured, renegotiate the sources in step 1 before writing any artifact.
|
||||
- `cronjob` for the recurring tick.
|
||||
- Optional: the `desktop_preview` tool (Hermes desktop app sessions). When it is in the toolset, dashboards render in the in-app preview pane; otherwise the user is given the file path.
|
||||
|
||||
## Procedure — Setup (foreground, once)
|
||||
|
||||
### 1. Define the dashboard contract
|
||||
@@ -38,39 +48,48 @@ For each source, do one bounded foreground read now: email/calendar via the conn
|
||||
|
||||
### 3. Build the dashboard artifact
|
||||
|
||||
Write two files under `~/.hermes/dashboards/<slug>/`:
|
||||
Write two files under the Hermes home directory's `dashboards/<slug>/` (the same directory that holds `config.yaml`; never assume a fixed location):
|
||||
|
||||
- `dashboard.json` — the contract plus current state: purpose, entities, per-field values, per-field source + retrieval timestamp, a `needs_attention` list, and a change log (append-only, most recent first).
|
||||
- `index.html` — a single self-contained HTML page (inline CSS, no external requests) rendering the state: a header with purpose and last-updated time, a "Needs attention" section on top, the entity table, and the recent-changes list. Regenerate it from `dashboard.json` on every refresh; never hand-edit HTML state.
|
||||
|
||||
Populate both from the step-2 reads and tell the user the file path (and open it where the platform allows). Done when the page renders the live data and every value on it carries a retrieval timestamp in `dashboard.json`.
|
||||
Populate both from the step-2 reads, then show the result (step 8). Done when the page renders the live data and every value on it carries a retrieval timestamp in `dashboard.json`.
|
||||
|
||||
### 4. Schedule the refresh
|
||||
|
||||
Only after step 3 succeeded, create the job:
|
||||
Only after step 3 succeeded, schedule the tick. If the daily all-dashboards sweep from `/suggestions` is already scheduled and its cadence suits, it will pick this dashboard up — say so and stop. Otherwise create a per-dashboard job whose prompt names the state file:
|
||||
|
||||
```
|
||||
cronjob(action="create",
|
||||
schedule=<cadence from the contract, e.g. "0 8 * * *">,
|
||||
prompt="Load the live-dashboard skill and run the refresh tick for the dashboard at ~/.hermes/dashboards/<slug>/dashboard.json.",
|
||||
prompt="Load the live-dashboard skill and run the refresh tick for the dashboard at <absolute path to dashboards/<slug>/dashboard.json>.",
|
||||
deliver=<user's destination>)
|
||||
```
|
||||
|
||||
Pick a cadence that respects source rate limits. Done when the job exists and its prompt names the state-file path.
|
||||
Pick a cadence that respects source rate limits. Done when a job covering this dashboard exists and its prompt names the state-file path (or the sweep).
|
||||
|
||||
## Procedure — Tick (each scheduled run)
|
||||
|
||||
### 5. Re-read sources and diff
|
||||
|
||||
Load `dashboard.json`, re-read each field from its named source, and compute a field-level diff against the stored state. A failed source read means unknown state: keep the last good value, mark the field stale with the failure time, and never overwrite good data with an error. Done when every field is either updated, unchanged, or explicitly marked stale.
|
||||
Load `dashboard.json` (for the sweep: every `dashboards/*/dashboard.json`, one at a time), re-read each field from its named source, and compute a field-level diff against the stored state. A failed source read means unknown state: keep the last good value, mark the field stale with the failure time, and never overwrite good data with an error. Done when every field is either updated, unchanged, or explicitly marked stale.
|
||||
|
||||
### 6. Update state and re-render
|
||||
|
||||
Apply the diff to `dashboard.json`: update values and timestamps, append material changes to the change log, and recompute `needs_attention` against the contract's attention rules. Regenerate `index.html` from the updated state. Done when the JSON and HTML agree and the change log entry for this run exists (or the run is recorded as no-change).
|
||||
Apply the diff to `dashboard.json`: update values and timestamps, append material changes to the change log, and recompute `needs_attention` against the contract's attention rules. Regenerate `index.html` from the updated state, then show the result (step 8). Done when the JSON and HTML agree and the change log entry for this run exists (or the run is recorded as no-change).
|
||||
|
||||
### 7. Deliver on material change, else stay silent
|
||||
|
||||
If the diff contains material changes or new needs-attention items, deliver a short summary: what changed, what needs attention, and the dashboard path. Otherwise respond with `[SILENT]` — no "still watching" noise unless the user asked for a periodic digest. Done when delivery matches the diff.
|
||||
If the diff contains material changes or new needs-attention items, deliver a short summary: what changed, what needs attention, and where the dashboard is. Otherwise respond with `[SILENT]` — no "still watching" noise unless the user asked for a periodic digest. Done when delivery matches the diff.
|
||||
|
||||
## Procedure — Show the dashboard (after every build or re-render, and on request)
|
||||
|
||||
### 8. Render where the user can see it
|
||||
|
||||
- Desktop/GUI session (`desktop_preview` is in the toolset): `desktop_preview(action="open", url=<absolute path to index.html>, label=<dashboard purpose>)` so the page renders live in the preview pane beside the chat; re-open after each re-render so the pane shows the new state.
|
||||
- Any other session (CLI, messenger, cron tick without a GUI): report the absolute path of `index.html` and offer to open it where the platform allows.
|
||||
|
||||
Done when the user has either seen the rendered page or been told exactly where it lives.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
@@ -79,6 +98,8 @@ If the diff contains material changes or new needs-attention items, deliver a sh
|
||||
- Rendering state into HTML only — `dashboard.json` is the source of truth; HTML is a projection.
|
||||
- Alerting on every refresh instead of on material change.
|
||||
- Tracking the wrong grain (per-thread when the user thinks per-application).
|
||||
- Hardcoding the Hermes home path — resolve it from the running install (the directory holding `config.yaml`) and write absolute paths into cron prompts.
|
||||
- Calling `desktop_preview` outside a desktop session — it is only in the toolset for GUI sessions; fall back to the path.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -87,3 +108,4 @@ If the diff contains material changes or new needs-attention items, deliver a sh
|
||||
- [ ] Failed reads marked fields stale without destroying last-known-good state.
|
||||
- [ ] Ticks deliver only on material change; no-change runs were `[SILENT]`.
|
||||
- [ ] The change log replays the dashboard's history from the state file alone.
|
||||
- [ ] The dashboard was shown in the preview pane (desktop) or its path reported (elsewhere).
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for the live-dashboard skill and its live-dashboard blueprint.
|
||||
"""Tests for the live-dashboard optional skill.
|
||||
|
||||
Inspired by Energy's (getenergy.com) natural-language live dashboards —
|
||||
describe what you want to see in one sentence, get a persistent
|
||||
@@ -11,7 +11,7 @@ import yaml
|
||||
|
||||
SKILL_PATH = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "skills"
|
||||
/ "optional-skills"
|
||||
/ "productivity"
|
||||
/ "live-dashboard"
|
||||
/ "SKILL.md"
|
||||
@@ -28,10 +28,6 @@ def _frontmatter_and_body():
|
||||
return fm, body
|
||||
|
||||
|
||||
def test_skill_file_exists():
|
||||
assert SKILL_PATH.is_file()
|
||||
|
||||
|
||||
def test_frontmatter_required_fields():
|
||||
fm, _ = _frontmatter_and_body()
|
||||
for field in ("name", "description", "version", "author", "license", "platforms"):
|
||||
@@ -39,13 +35,6 @@ def test_frontmatter_required_fields():
|
||||
assert fm["name"] == "live-dashboard"
|
||||
|
||||
|
||||
def test_description_hardline():
|
||||
fm, _ = _frontmatter_and_body()
|
||||
desc = fm["description"]
|
||||
assert len(desc) <= 60, f"description is {len(desc)} chars; hardline is 60"
|
||||
assert desc.endswith(".")
|
||||
|
||||
|
||||
def test_related_skills_resolve_in_repo():
|
||||
fm, _ = _frontmatter_and_body()
|
||||
repo_root = SKILL_PATH.parents[3]
|
||||
@@ -72,6 +61,8 @@ def test_state_discipline_present():
|
||||
assert "dashboard.json" in body
|
||||
assert "source of truth" in body
|
||||
assert "last-known-good" in body or "last good value" in body
|
||||
assert "never hand-edit HTML state" in body
|
||||
assert "[SILENT]" in body, "no-change ticks must stay silent"
|
||||
|
||||
|
||||
def test_source_verification_before_scheduling():
|
||||
@@ -80,55 +71,31 @@ def test_source_verification_before_scheduling():
|
||||
assert "one bounded foreground read" in body
|
||||
|
||||
|
||||
def test_silent_path_explicit():
|
||||
_, body = _frontmatter_and_body()
|
||||
assert "[SILENT]" in body, "no-change ticks must stay silent"
|
||||
|
||||
|
||||
def test_steps_have_completion_criteria():
|
||||
_, body = _frontmatter_and_body()
|
||||
steps = re.findall(r"^### \d+\..*?(?=^### \d+\.|^## )", body, re.MULTILINE | re.DOTALL)
|
||||
assert len(steps) >= 6
|
||||
assert len(steps) >= 7
|
||||
for step in steps:
|
||||
assert "Done when" in step, f"step missing completion criterion: {step[:60]!r}"
|
||||
|
||||
|
||||
def test_html_is_projection_not_state():
|
||||
def test_desktop_preview_with_path_fallback():
|
||||
"""Desktop sessions render in the preview pane; everything else gets the file path.
|
||||
The Hermes home directory is never hardcoded in prose the agent executes."""
|
||||
_, body = _frontmatter_and_body()
|
||||
assert "never hand-edit HTML state" in body
|
||||
assert "self-contained HTML" in body
|
||||
assert 'desktop_preview(action="open"' in body
|
||||
assert "report the absolute path" in body
|
||||
assert "~/.hermes" not in body
|
||||
|
||||
|
||||
def test_live_dashboard_blueprint_registered():
|
||||
from cron.blueprint_catalog import CATALOG
|
||||
def test_frontmatter_blueprint_is_a_valid_installed_blueprint():
|
||||
"""The install-time suggestion rides the skills-pipeline blueprint block, not a
|
||||
hard-wired catalog entry (an optional skill may not be installed)."""
|
||||
from tools.blueprints import blueprint_to_job_spec, parse_blueprint
|
||||
|
||||
bp = next((b for b in CATALOG if b.key == "live-dashboard"), None)
|
||||
assert bp is not None, "live-dashboard blueprint missing from catalog"
|
||||
assert "live-dashboard" in bp.skills, "blueprint must load the skill"
|
||||
slot_names = {s.name for s in bp.slots}
|
||||
assert {"purpose", "sources", "time", "recurrence", "deliver"} <= slot_names
|
||||
assert "[SILENT]" in bp.prompt_template, "silent path must be explicit"
|
||||
assert "{purpose}" in bp.prompt_template and "{sources}" in bp.prompt_template
|
||||
|
||||
|
||||
def test_live_dashboard_blueprint_fills():
|
||||
"""fill_blueprint must produce a valid cron job kwargs dict."""
|
||||
from cron.blueprint_catalog import CATALOG, fill_blueprint
|
||||
|
||||
bp = next(b for b in CATALOG if b.key == "live-dashboard")
|
||||
job = fill_blueprint(
|
||||
bp,
|
||||
{
|
||||
"purpose": "team visa applications",
|
||||
"sources": "email threads and the case-status site",
|
||||
"time": "07:30",
|
||||
"recurrence": "weekdays",
|
||||
"deliver": "origin",
|
||||
},
|
||||
)
|
||||
assert "team visa applications" in job["prompt"]
|
||||
assert "email threads and the case-status site" in job["prompt"]
|
||||
fields = job["schedule"].split()
|
||||
assert len(fields) == 5, f"invalid cron expr: {job['schedule']}"
|
||||
assert fields[0] == "30" and fields[1] == "7"
|
||||
assert fields[4] == "1-5"
|
||||
spec = parse_blueprint(SKILL_PATH.read_text(encoding="utf-8"))
|
||||
assert spec is not None and spec.skill_name == "live-dashboard"
|
||||
job = blueprint_to_job_spec(spec)
|
||||
assert job["skills"] == ["live-dashboard"]
|
||||
assert len(job["schedule"].split()) == 5, f"invalid cron expr: {job['schedule']}"
|
||||
assert "[SILENT]" in job["prompt"] and "~/.hermes" not in job["prompt"]
|
||||
|
||||
@@ -213,6 +213,7 @@ hermes skills uninstall <skill-name>
|
||||
| [**canvas**](/docs/user-guide/skills/optional/productivity/productivity-canvas) | Fetch Canvas LMS courses and assignments via API token. |
|
||||
| [**decision-questionnaire**](/docs/user-guide/skills/optional/productivity/productivity-decision-questionnaire) | Turn an unanswerable decision into a questionnaire doc. |
|
||||
| [**here-now**](/docs/user-guide/skills/optional/productivity/productivity-here-now) | Publish sites to {slug}.here.now and store files in Drives. |
|
||||
| [**live-dashboard**](/docs/user-guide/skills/optional/productivity/productivity-live-dashboard) | Build self-updating dashboards from live sources. |
|
||||
| [**memento-flashcards**](/docs/user-guide/skills/optional/productivity/productivity-memento-flashcards) | Spaced-repetition flashcards: create, review, quiz, export. |
|
||||
| [**property-listings**](/docs/user-guide/skills/optional/productivity/productivity-property-listings) | Present property and rental listings as desktop cards. |
|
||||
| [**shop**](/docs/user-guide/skills/optional/productivity/productivity-shop) | Shop catalog search, checkout, order tracking, returns. |
|
||||
|
||||
@@ -14,9 +14,9 @@ Build self-updating dashboards from live sources.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Bundled (installed by default) |
|
||||
| Path | `skills/productivity/live-dashboard` |
|
||||
| Version | `0.1.0` |
|
||||
| Source | Optional — install with `hermes skills install official/productivity/live-dashboard` |
|
||||
| Path | `optional-skills/productivity/live-dashboard` |
|
||||
| Version | `0.2.0` |
|
||||
| Author | Hermes Agent |
|
||||
| License | MIT |
|
||||
| Platforms | linux, macos, windows |
|
||||
@@ -33,17 +33,24 @@ The following is the complete skill definition that Hermes loads when this skill
|
||||
|
||||
Turn one sentence — "make a dashboard for our visa applications, update it daily from the email threads and the case-status site" — into a persistent, self-refreshing status page. The user describes what they want to see; you define the data contract, build a self-contained HTML dashboard, verify one live refresh, then schedule the recurring tick. Inspired by Energy's (getenergy.com) natural-language live dashboards, adapted to Hermes's cron + connector architecture.
|
||||
|
||||
Setup runs once in the foreground; the recurring refresh runs as a `cronjob` tick (the `live-dashboard` automation blueprint scaffolds this).
|
||||
Setup runs once in the foreground; the recurring refresh runs as a `cronjob` tick. Installing this skill offers a daily all-dashboards sweep via `/suggestions` (the frontmatter blueprint).
|
||||
|
||||
## When to Use
|
||||
|
||||
- "Make a dashboard for <project/process> and keep it updated."
|
||||
- "I want one place to see the status of <deals / applications / bugs / shipments>."
|
||||
- "Track <thing> across my email and <website> and show me where it stands."
|
||||
- "Show me the <slug> dashboard." (re-render/preview an existing one)
|
||||
- A cron tick fires for an existing dashboard (steps 5-7).
|
||||
|
||||
Don't use for: one-off status questions (answer directly), price/availability thresholds on a single item (use `product-price-monitor`), or company news tracking (use `competitor-news-monitor`).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- At least one source connector the dashboard will read from: email/calendar via `himalaya` or `google-workspace`, websites via `web_extract` or `browser_navigate`, local files via `read_file`. If none is configured, renegotiate the sources in step 1 before writing any artifact.
|
||||
- `cronjob` for the recurring tick.
|
||||
- Optional: the `desktop_preview` tool (Hermes desktop app sessions). When it is in the toolset, dashboards render in the in-app preview pane; otherwise the user is given the file path.
|
||||
|
||||
## Procedure — Setup (foreground, once)
|
||||
|
||||
### 1. Define the dashboard contract
|
||||
@@ -56,39 +63,48 @@ For each source, do one bounded foreground read now: email/calendar via the conn
|
||||
|
||||
### 3. Build the dashboard artifact
|
||||
|
||||
Write two files under `~/.hermes/dashboards/<slug>/`:
|
||||
Write two files under the Hermes home directory's `dashboards/<slug>/` (the same directory that holds `config.yaml`; never assume a fixed location):
|
||||
|
||||
- `dashboard.json` — the contract plus current state: purpose, entities, per-field values, per-field source + retrieval timestamp, a `needs_attention` list, and a change log (append-only, most recent first).
|
||||
- `index.html` — a single self-contained HTML page (inline CSS, no external requests) rendering the state: a header with purpose and last-updated time, a "Needs attention" section on top, the entity table, and the recent-changes list. Regenerate it from `dashboard.json` on every refresh; never hand-edit HTML state.
|
||||
|
||||
Populate both from the step-2 reads and tell the user the file path (and open it where the platform allows). Done when the page renders the live data and every value on it carries a retrieval timestamp in `dashboard.json`.
|
||||
Populate both from the step-2 reads, then show the result (step 8). Done when the page renders the live data and every value on it carries a retrieval timestamp in `dashboard.json`.
|
||||
|
||||
### 4. Schedule the refresh
|
||||
|
||||
Only after step 3 succeeded, create the job:
|
||||
Only after step 3 succeeded, schedule the tick. If the daily all-dashboards sweep from `/suggestions` is already scheduled and its cadence suits, it will pick this dashboard up — say so and stop. Otherwise create a per-dashboard job whose prompt names the state file:
|
||||
|
||||
```
|
||||
cronjob(action="create",
|
||||
schedule=<cadence from the contract, e.g. "0 8 * * *">,
|
||||
prompt="Load the live-dashboard skill and run the refresh tick for the dashboard at ~/.hermes/dashboards/<slug>/dashboard.json.",
|
||||
prompt="Load the live-dashboard skill and run the refresh tick for the dashboard at <absolute path to dashboards/<slug>/dashboard.json>.",
|
||||
deliver=<user's destination>)
|
||||
```
|
||||
|
||||
Pick a cadence that respects source rate limits. Done when the job exists and its prompt names the state-file path.
|
||||
Pick a cadence that respects source rate limits. Done when a job covering this dashboard exists and its prompt names the state-file path (or the sweep).
|
||||
|
||||
## Procedure — Tick (each scheduled run)
|
||||
|
||||
### 5. Re-read sources and diff
|
||||
|
||||
Load `dashboard.json`, re-read each field from its named source, and compute a field-level diff against the stored state. A failed source read means unknown state: keep the last good value, mark the field stale with the failure time, and never overwrite good data with an error. Done when every field is either updated, unchanged, or explicitly marked stale.
|
||||
Load `dashboard.json` (for the sweep: every `dashboards/*/dashboard.json`, one at a time), re-read each field from its named source, and compute a field-level diff against the stored state. A failed source read means unknown state: keep the last good value, mark the field stale with the failure time, and never overwrite good data with an error. Done when every field is either updated, unchanged, or explicitly marked stale.
|
||||
|
||||
### 6. Update state and re-render
|
||||
|
||||
Apply the diff to `dashboard.json`: update values and timestamps, append material changes to the change log, and recompute `needs_attention` against the contract's attention rules. Regenerate `index.html` from the updated state. Done when the JSON and HTML agree and the change log entry for this run exists (or the run is recorded as no-change).
|
||||
Apply the diff to `dashboard.json`: update values and timestamps, append material changes to the change log, and recompute `needs_attention` against the contract's attention rules. Regenerate `index.html` from the updated state, then show the result (step 8). Done when the JSON and HTML agree and the change log entry for this run exists (or the run is recorded as no-change).
|
||||
|
||||
### 7. Deliver on material change, else stay silent
|
||||
|
||||
If the diff contains material changes or new needs-attention items, deliver a short summary: what changed, what needs attention, and the dashboard path. Otherwise respond with `[SILENT]` — no "still watching" noise unless the user asked for a periodic digest. Done when delivery matches the diff.
|
||||
If the diff contains material changes or new needs-attention items, deliver a short summary: what changed, what needs attention, and where the dashboard is. Otherwise respond with `[SILENT]` — no "still watching" noise unless the user asked for a periodic digest. Done when delivery matches the diff.
|
||||
|
||||
## Procedure — Show the dashboard (after every build or re-render, and on request)
|
||||
|
||||
### 8. Render where the user can see it
|
||||
|
||||
- Desktop/GUI session (`desktop_preview` is in the toolset): `desktop_preview(action="open", url=<absolute path to index.html>, label=<dashboard purpose>)` so the page renders live in the preview pane beside the chat; re-open after each re-render so the pane shows the new state.
|
||||
- Any other session (CLI, messenger, cron tick without a GUI): report the absolute path of `index.html` and offer to open it where the platform allows.
|
||||
|
||||
Done when the user has either seen the rendered page or been told exactly where it lives.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
@@ -97,6 +113,8 @@ If the diff contains material changes or new needs-attention items, deliver a sh
|
||||
- Rendering state into HTML only — `dashboard.json` is the source of truth; HTML is a projection.
|
||||
- Alerting on every refresh instead of on material change.
|
||||
- Tracking the wrong grain (per-thread when the user thinks per-application).
|
||||
- Hardcoding the Hermes home path — resolve it from the running install (the directory holding `config.yaml`) and write absolute paths into cron prompts.
|
||||
- Calling `desktop_preview` outside a desktop session — it is only in the toolset for GUI sessions; fall back to the path.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -105,3 +123,4 @@ If the diff contains material changes or new needs-attention items, deliver a sh
|
||||
- [ ] Failed reads marked fields stale without destroying last-known-good state.
|
||||
- [ ] Ticks deliver only on material change; no-change runs were `[SILENT]`.
|
||||
- [ ] The change log replays the dashboard's history from the state file alone.
|
||||
- [ ] The dashboard was shown in the preview pane (desktop) or its path reported (elsewhere).
|
||||
@@ -247,7 +247,6 @@ const sidebars: SidebarsConfig = {
|
||||
'user-guide/skills/bundled/productivity/productivity-document-to-action-items',
|
||||
'user-guide/skills/bundled/productivity/productivity-docx',
|
||||
'user-guide/skills/bundled/productivity/productivity-google-workspace',
|
||||
'user-guide/skills/bundled/productivity/productivity-live-dashboard',
|
||||
'user-guide/skills/bundled/productivity/productivity-maps',
|
||||
'user-guide/skills/bundled/productivity/productivity-meeting-action-items',
|
||||
'user-guide/skills/bundled/productivity/productivity-notion',
|
||||
@@ -547,6 +546,7 @@ const sidebars: SidebarsConfig = {
|
||||
'user-guide/skills/optional/productivity/productivity-canvas',
|
||||
'user-guide/skills/optional/productivity/productivity-decision-questionnaire',
|
||||
'user-guide/skills/optional/productivity/productivity-here-now',
|
||||
'user-guide/skills/optional/productivity/productivity-live-dashboard',
|
||||
'user-guide/skills/optional/productivity/productivity-memento-flashcards',
|
||||
'user-guide/skills/optional/productivity/productivity-property-listings',
|
||||
'user-guide/skills/optional/productivity/productivity-shop',
|
||||
|
||||
Reference in New Issue
Block a user