mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-15 09:21:36 +00:00
A 'recipe' is a one-place definition of an automation that every surface renders natively. The slot schema (cron/recipe_catalog.py) is the single source of truth; four renderers consume it, and all paths end at the same cron.jobs.create_job — no second job engine. Form where there's a screen, conversation where there's a chat line: - Dashboard / GUI app: a Recipes sub-tab on the Cron page renders each recipe's typed slots as a form (time-picker, enum dropdown, free-text); submit POSTs /api/cron/recipes/instantiate which fills + creates the job. - CLI / TUI / messengers: /cron-recipe lists the catalog, shows a recipe's fields, or fills + creates from a pasted 'key slot=val' command. The shared handler (hermes_cli/cron_recipe_cmd.py) names any missing/invalid slot so the agent can ask a targeted follow-up. - Docs: a generated Cron Recipes catalog page (website, .mdx + React cards) shows each recipe with a copy-paste command and a 'Send to App' button. - Desktop: a hermes:// URL scheme (Electron single-instance lock + setAsDefaultProtocolClient + open-url/second-instance) routes hermes://cron-recipe/<key>?slot=val into the chat composer pre-filled. Typed slots (time/enum/text/weekdays) with defaults: users never type raw cron — recipes parameterize time-of-day and weekday sets and translate to cron expressions; a free-text 'schedule' slot is the full-flexibility escape hatch. Consent-first throughout: nothing schedules without an explicit submit or send. Core: - cron/recipe_catalog.py — CronRecipe + RecipeSlot, 5 curated recipes, recipe_form_schema / recipe_slash_command / recipe_deeplink / recipe_catalog_entry renderers, fill_recipe (validate + translate to create_job kwargs). - hermes_cli/cron_recipe_cmd.py — shared /cron-recipe handler (CLI + TUI + gateway never drift). CommandDef + dispatch in commands.py / cli.py / gateway/run.py. Dashboard: GET /api/cron/recipes + POST /api/cron/recipes/instantiate (web_server.py), CronRecipes.tsx gallery+form, Segmented sub-tab on CronPage, api.ts methods + types. Desktop: hermes:// scheme end to end (main.cjs deep-link router + ready-queue, preload onDeepLink/signalDeepLinkReady, global.d.ts types, desktop-controller composer prefill, electron-builder protocols key). Docs: extract-cron-recipes.py generator wired into prebuild.mjs, cron-recipes-catalog.mdx + CronRecipesCatalog React component, sidebar entry. Generated index json gitignored like skills.json. Tests: 23 core (catalog/slots/schedule-resolution/validation/renderers/command handler/generator) + 5 web_server endpoint tests. E2E verified end to end: slot fill -> create_job -> persisted job with correct schedule/deliver/origin.
50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the Cron Recipes catalog JSON for the docs site.
|
|
|
|
Mirrors ``extract-skills.py``: imports the single-source-of-truth recipe
|
|
definitions from ``cron/recipe_catalog.py`` and emits a flat JSON array the
|
|
docs page renders into cards (description, schedule, copy-paste slash command,
|
|
and a ``hermes://`` "Send to App" deep-link).
|
|
|
|
Output: ``website/static/api/cron-recipes-index.json`` (served at
|
|
``/docs/api/cron-recipes-index.json``). Run automatically by
|
|
``website/scripts/prebuild.mjs`` before ``npm start`` / ``npm run build``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Repo root = two levels up from website/scripts/.
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
OUTPUT = REPO_ROOT / "website" / "static" / "api" / "cron-recipes-index.json"
|
|
|
|
|
|
def build_index() -> list:
|
|
from cron.recipe_catalog import CATALOG, recipe_catalog_entry
|
|
|
|
return [recipe_catalog_entry(r) for r in CATALOG]
|
|
|
|
|
|
def main() -> int:
|
|
try:
|
|
index = build_index()
|
|
except Exception as e: # pragma: no cover - import/build failure
|
|
# Match extract-skills.py's resilience: write an empty array so the
|
|
# docs build never hard-fails on a generator hiccup.
|
|
sys.stderr.write(f"extract-cron-recipes: {e}; writing empty index\n")
|
|
index = []
|
|
|
|
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(OUTPUT, "w", encoding="utf-8") as f:
|
|
json.dump(index, f, separators=(",", ":"))
|
|
sys.stderr.write(f"extract-cron-recipes: wrote {len(index)} recipes -> {OUTPUT}\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|