refactor(cron): rebrand Cron Recipes -> Automation Blueprints
Product rename across every surface: module/file names (blueprint_catalog, tools/blueprints, blueprint_cmd), slash command /cron-recipe -> /blueprint (alias /bp), dashboard API /api/cron/blueprints, desktop deep-link hermes://blueprint/<key>, docs catalog page + extract script, and the skill frontmatter block metadata.hermes.blueprint. No behavior change.
This commit is contained in:
@@ -66,7 +66,7 @@ metadata:
|
||||
description: "What this setting controls"
|
||||
default: "sensible-default"
|
||||
prompt: "Display prompt for setup"
|
||||
recipe: # Optional — marks this skill a runnable automation
|
||||
blueprint: # Optional — marks this skill a runnable automation
|
||||
schedule: "0 9 * * *" # cron expr / "every 2h" / ISO timestamp
|
||||
deliver: origin # optional (default origin)
|
||||
prompt: "Task instruction for each run" # optional
|
||||
@@ -339,28 +339,28 @@ If your skill is official and useful but not universally needed (e.g., a paid se
|
||||
|
||||
If your skill is specialized, community-contributed, or niche, it's better suited for a **Skills Hub** — upload it to a registry and share it via `hermes skills install`.
|
||||
|
||||
## Recipes: skills that are also automations
|
||||
## Blueprints: skills that are also automations
|
||||
|
||||
A **recipe** is an ordinary skill that additionally declares a schedule in its frontmatter. Add a `metadata.hermes.recipe` block and the skill becomes a shareable, runnable automation:
|
||||
A **blueprint** is an ordinary skill that additionally declares a schedule in its frontmatter. Add a `metadata.hermes.blueprint` block and the skill becomes a shareable, runnable automation:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [recipe, email]
|
||||
recipe:
|
||||
schedule: "0 8 * * *" # presence of `recipe:` marks it runnable
|
||||
tags: [blueprint, email]
|
||||
blueprint:
|
||||
schedule: "0 8 * * *" # presence of `blueprint:` marks it runnable
|
||||
deliver: telegram # optional (default: origin)
|
||||
prompt: "Summarize my unread email and today's calendar." # optional
|
||||
no_agent: false # optional
|
||||
```
|
||||
|
||||
Because a recipe **is** a skill, it flows through the entire skills pipeline unchanged — search, inspect, install, security scan, provenance, taps, the centralized index, and `hermes skills publish` for sharing. Nothing new to learn.
|
||||
Because a blueprint **is** a skill, it flows through the entire skills pipeline unchanged — search, inspect, install, security scan, provenance, taps, the centralized index, and `hermes skills publish` for sharing. Nothing new to learn.
|
||||
|
||||
**Installing a recipe.** When you install a skill that carries a `recipe:` block, Hermes registers it as a **suggested cron job** rather than scheduling it. Scheduling is **opt-in** — installing never silently creates a recurring job. You review and accept it via `/suggestions`:
|
||||
**Installing a blueprint.** When you install a skill that carries a `blueprint:` block, Hermes registers it as a **suggested cron job** rather than scheduling it. Scheduling is **opt-in** — installing never silently creates a recurring job. You review and accept it via `/suggestions`:
|
||||
|
||||
```bash
|
||||
hermes skills install owner/morning-brief
|
||||
# → Recipe: 'morning-brief' is an automation (schedule 0 8 * * *).
|
||||
# → Blueprint: 'morning-brief' is an automation (schedule 0 8 * * *).
|
||||
# Added to your suggestions — run /suggestions to schedule or dismiss it.
|
||||
|
||||
# then, in a session:
|
||||
@@ -369,11 +369,11 @@ hermes skills install owner/morning-brief
|
||||
/suggestions dismiss 1 # never offer it again
|
||||
```
|
||||
|
||||
Recipes are one **source** of the unified Suggested Cron Jobs surface — the same place curated starter automations and (later) usage-pattern and integration suggestions appear. See [Suggested Cron Jobs](#suggested-cron-jobs) below.
|
||||
Blueprints are one **source** of the unified Suggested Cron Jobs surface — the same place curated starter automations and (later) usage-pattern and integration suggestions appear. See [Suggested Cron Jobs](#suggested-cron-jobs) below.
|
||||
|
||||
**Sharing an automation you built.** A recipe loaded by a cron job (`hermes cron create --skill <name> ...`) can be exported back to a SKILL.md and published like any other skill, so an automation you tuned for yourself becomes a one-command install for someone else.
|
||||
**Sharing an automation you built.** A blueprint loaded by a cron job (`hermes cron create --skill <name> ...`) can be exported back to a SKILL.md and published like any other skill, so an automation you tuned for yourself becomes a one-command install for someone else.
|
||||
|
||||
The recipe layer adds no new object type, store, or transport — the recipe is a skill, the schedule is a cron job, and sharing is the existing publish/tap/index path.
|
||||
The blueprint layer adds no new object type, store, or transport — the blueprint is a skill, the schedule is a cron job, and sharing is the existing publish/tap/index path.
|
||||
|
||||
## Suggested Cron Jobs
|
||||
|
||||
@@ -382,7 +382,7 @@ Hermes can *propose* automations and let you accept them with one tap, instead o
|
||||
| Source | Trigger |
|
||||
|--------|---------|
|
||||
| `catalog` | Curated starter automations (`/suggestions catalog`) — daily briefing, important-mail monitor, weekly review, workday-start reminder |
|
||||
| `recipe` | You installed a skill carrying a `recipe:` block |
|
||||
| `blueprint` | You installed a skill carrying a `blueprint:` block |
|
||||
| `usage` | The background review noticed a recurring ask a schedule would serve |
|
||||
| `integration` | You connected an account (Gmail, GitHub, ...) and the obvious automations are offered |
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
title: "Automation Blueprints Catalog"
|
||||
description: "Ready-to-run automation templates — set one up from the dashboard, CLI, TUI, any messenger, or the desktop app."
|
||||
---
|
||||
|
||||
import AutomationBlueprintsCatalog from '@site/src/components/AutomationBlueprintsCatalog';
|
||||
|
||||
# Automation Blueprints
|
||||
|
||||
Automation Blueprints are ready-to-run automation templates. Pick one, fill in a couple
|
||||
of fields, and Hermes schedules it as a cron job — no cron syntax required.
|
||||
|
||||
Every blueprint works from **every surface**:
|
||||
|
||||
- **Dashboard / desktop app** — open the Cron page, switch to the **Blueprints**
|
||||
tab, fill the form, and click *Schedule it*.
|
||||
- **CLI, TUI, and messengers** — type `/blueprint <name>` (e.g.
|
||||
`/blueprint morning-brief`) and Hermes asks you for what it needs, one
|
||||
question at a time, then schedules it. The name match is forgiving — a
|
||||
prefix or near-spelling resolves. Power users can skip the questions by
|
||||
passing values inline: `/blueprint morning-brief time=08:00`.
|
||||
- **Desktop app** — click **Send to App** on any blueprint and it opens with the
|
||||
command pre-loaded in your composer.
|
||||
|
||||
Blueprints never schedule anything silently — you always confirm before the job
|
||||
is created. Manage created jobs anytime with `/cron`.
|
||||
|
||||
<AutomationBlueprintsCatalog />
|
||||
|
||||
## Writing your own
|
||||
|
||||
A blueprint is just a skill with a `metadata.hermes.blueprint` block in its
|
||||
`SKILL.md` frontmatter. See
|
||||
[Creating Skills → Automation Blueprints](../developer-guide/creating-skills.md) for the
|
||||
slot schema and how to publish one.
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
sidebar_position: 7
|
||||
title: "Cron Recipes Catalog"
|
||||
description: "Ready-to-run automation templates — set one up from the dashboard, CLI, TUI, any messenger, or the desktop app."
|
||||
---
|
||||
|
||||
import CronRecipesCatalog from '@site/src/components/CronRecipesCatalog';
|
||||
|
||||
# Cron Recipes
|
||||
|
||||
Cron Recipes are ready-to-run automation templates. Pick one, fill in a couple
|
||||
of fields, and Hermes schedules it as a cron job — no cron syntax required.
|
||||
|
||||
Every recipe works from **every surface**:
|
||||
|
||||
- **Dashboard / desktop app** — open the Cron page, switch to the **Recipes**
|
||||
tab, fill the form, and click *Schedule it*.
|
||||
- **CLI, TUI, and messengers** — type `/cron-recipe <name>` (e.g.
|
||||
`/cron-recipe morning-brief`) and Hermes asks you for what it needs, one
|
||||
question at a time, then schedules it. The name match is forgiving — a
|
||||
prefix or near-spelling resolves. Power users can skip the questions by
|
||||
passing values inline: `/cron-recipe morning-brief time=08:00`.
|
||||
- **Desktop app** — click **Send to App** on any recipe and it opens with the
|
||||
command pre-loaded in your composer.
|
||||
|
||||
Recipes never schedule anything silently — you always confirm before the job
|
||||
is created. Manage created jobs anytime with `/cron`.
|
||||
|
||||
<CronRecipesCatalog />
|
||||
|
||||
## Writing your own
|
||||
|
||||
A recipe is just a skill with a `metadata.hermes.recipe` block in its
|
||||
`SKILL.md` frontmatter. See
|
||||
[Creating Skills → Cron Recipes](../developer-guide/creating-skills.md) for the
|
||||
slot schema and how to publish one.
|
||||
+10
-10
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the Cron Recipes catalog JSON for the docs site.
|
||||
"""Generate the Automation Blueprints 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
|
||||
Mirrors ``extract-skills.py``: imports the single-source-of-truth blueprint
|
||||
definitions from ``cron/blueprint_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
|
||||
Output: ``website/static/api/automation-blueprints-index.json`` (served at
|
||||
``/docs/api/automation-blueprints-index.json``). Run automatically by
|
||||
``website/scripts/prebuild.mjs`` before ``npm start`` / ``npm run build``.
|
||||
"""
|
||||
|
||||
@@ -21,13 +21,13 @@ from pathlib import Path
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
OUTPUT = REPO_ROOT / "website" / "static" / "api" / "cron-recipes-index.json"
|
||||
OUTPUT = REPO_ROOT / "website" / "static" / "api" / "automation-blueprints-index.json"
|
||||
|
||||
|
||||
def build_index() -> list:
|
||||
from cron.recipe_catalog import CATALOG, recipe_catalog_entry
|
||||
from cron.blueprint_catalog import CATALOG, blueprint_catalog_entry
|
||||
|
||||
return [recipe_catalog_entry(r) for r in CATALOG]
|
||||
return [blueprint_catalog_entry(r) for r in CATALOG]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
@@ -36,13 +36,13 @@ def main() -> int:
|
||||
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")
|
||||
sys.stderr.write(f"extract-automation-blueprints: {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")
|
||||
sys.stderr.write(f"extract-automation-blueprints: wrote {len(index)} blueprints -> {OUTPUT}\n")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const websiteDir = resolve(scriptDir, "..");
|
||||
const extractScript = join(scriptDir, "extract-skills.py");
|
||||
const llmsScript = join(scriptDir, "generate-llms-txt.py");
|
||||
const cronRecipesScript = join(scriptDir, "extract-cron-recipes.py");
|
||||
const cronBlueprintsScript = join(scriptDir, "extract-automation-blueprints.py");
|
||||
const outputFile = join(websiteDir, "static", "api", "skills.json");
|
||||
const unifiedIndexFile = join(websiteDir, "static", "api", "skills-index.json");
|
||||
const UNIFIED_INDEX_URL =
|
||||
@@ -140,6 +140,6 @@ if (!existsSync(extractScript)) {
|
||||
// 2) llms.txt + llms-full.txt — agent-friendly docs entrypoints. Non-fatal.
|
||||
runPython(llmsScript, "generate-llms-txt.py");
|
||||
|
||||
// 3) cron-recipes-index.json — Cron Recipes catalog page. Non-fatal; the page
|
||||
// 3) automation-blueprints-index.json — Automation Blueprints catalog page. Non-fatal; the page
|
||||
// renders an empty state if the generator can't run.
|
||||
runPython(cronRecipesScript, "extract-cron-recipes.py");
|
||||
runPython(cronBlueprintsScript, "extract-automation-blueprints.py");
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'Automation',
|
||||
items: [
|
||||
'user-guide/features/cron',
|
||||
'reference/cron-recipes-catalog',
|
||||
'reference/automation-blueprints-catalog',
|
||||
'user-guide/features/delegation',
|
||||
'user-guide/features/kanban',
|
||||
'user-guide/features/codex-app-server-runtime',
|
||||
|
||||
+23
-23
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
interface RecipeField {
|
||||
interface BlueprintField {
|
||||
name: string;
|
||||
type: string;
|
||||
label: string;
|
||||
@@ -11,19 +11,19 @@ interface RecipeField {
|
||||
help: string;
|
||||
}
|
||||
|
||||
interface Recipe {
|
||||
interface Blueprint {
|
||||
key: string;
|
||||
title: string;
|
||||
description: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
fields: RecipeField[];
|
||||
fields: BlueprintField[];
|
||||
scheduleHuman: string;
|
||||
command: string;
|
||||
appUrl: string;
|
||||
}
|
||||
|
||||
const INDEX_URL = "/docs/api/cron-recipes-index.json";
|
||||
const INDEX_URL = "/docs/api/automation-blueprints-index.json";
|
||||
|
||||
function CopyButton({ text }: { text: string }): JSX.Element {
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -44,17 +44,17 @@ function CopyButton({ text }: { text: string }): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function RecipeCard({ recipe }: { recipe: Recipe }): JSX.Element {
|
||||
function BlueprintCard({ blueprint }: { blueprint: Blueprint }): JSX.Element {
|
||||
return (
|
||||
<div className={styles.card}>
|
||||
<div className={styles.cardHead}>
|
||||
<h3 className={styles.title}>{recipe.title}</h3>
|
||||
<span className={styles.schedule}>{recipe.scheduleHuman}</span>
|
||||
<h3 className={styles.title}>{blueprint.title}</h3>
|
||||
<span className={styles.schedule}>{blueprint.scheduleHuman}</span>
|
||||
</div>
|
||||
<p className={styles.desc}>{recipe.description}</p>
|
||||
<p className={styles.desc}>{blueprint.description}</p>
|
||||
|
||||
<div className={styles.tags}>
|
||||
{recipe.tags.map((t) => (
|
||||
{blueprint.tags.map((t) => (
|
||||
<span key={t} className={styles.tag}>
|
||||
{t}
|
||||
</span>
|
||||
@@ -62,12 +62,12 @@ function RecipeCard({ recipe }: { recipe: Recipe }): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className={styles.cmdRow}>
|
||||
<code className={styles.cmd}>{recipe.command}</code>
|
||||
<CopyButton text={recipe.command} />
|
||||
<code className={styles.cmd}>{blueprint.command}</code>
|
||||
<CopyButton text={blueprint.command} />
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<a className={styles.appBtn} href={recipe.appUrl}>
|
||||
<a className={styles.appBtn} href={blueprint.appUrl}>
|
||||
Send to App ↗
|
||||
</a>
|
||||
<span className={styles.hint}>
|
||||
@@ -78,16 +78,16 @@ function RecipeCard({ recipe }: { recipe: Recipe }): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default function CronRecipesCatalog(): JSX.Element {
|
||||
const [recipes, setRecipes] = useState<Recipe[] | null>(null);
|
||||
export default function AutomationBlueprintsCatalog(): JSX.Element {
|
||||
const [blueprints, setBlueprints] = useState<Blueprint[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetch(INDEX_URL)
|
||||
.then((r) => r.json())
|
||||
.then((data: Recipe[]) => {
|
||||
if (!cancelled) setRecipes(data);
|
||||
.then((data: Blueprint[]) => {
|
||||
if (!cancelled) setBlueprints(data);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!cancelled) setError(String(e));
|
||||
@@ -98,19 +98,19 @@ export default function CronRecipesCatalog(): JSX.Element {
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <p>Couldn't load the recipe catalog: {error}</p>;
|
||||
return <p>Couldn't load the blueprint catalog: {error}</p>;
|
||||
}
|
||||
if (recipes === null) {
|
||||
return <p>Loading recipes…</p>;
|
||||
if (blueprints === null) {
|
||||
return <p>Loading blueprints…</p>;
|
||||
}
|
||||
if (recipes.length === 0) {
|
||||
return <p>No cron recipes are available.</p>;
|
||||
if (blueprints.length === 0) {
|
||||
return <p>No automation blueprints are available.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.grid}>
|
||||
{recipes.map((r) => (
|
||||
<RecipeCard key={r.key} recipe={r} />
|
||||
{blueprints.map((r) => (
|
||||
<BlueprintCard key={r.key} blueprint={r} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
Reference in New Issue
Block a user