| name | scarf-template-author |
| description | Scaffold a new Scarf project — dashboard, optional configuration schema, optional cron job, and AGENTS.md — from a short conversational interview with the user. Output is immediately usable locally and cleanly exportable as a .scarftemplate bundle. |
| version | 1.1.0 |
| author | Alan Wizemann |
| license | MIT |
| platforms | ["macos"] |
| metadata | {"hermes":{"tags":["Scarf","templates","scaffolding","dashboard","authoring"],"homepage":"https://github.com/awizemann/scarf/wiki/Project-Templates"}} |
| prerequisites | {"commands":["hermes"]} |
Scarf Template Author
Scaffold a new Scarf-compatible project from a conversational interview. The output is both (a) a working project on disk the user can register with Scarf and use immediately, and (b) correctly shaped to be exported as a .scarftemplate bundle via Scarf's Export flow later.
When to invoke this skill
Activate when the user says things like:
- "Create a new Scarf project that watches / tracks / reports on …"
- "Scaffold a dashboard for …"
- "Set up a project that runs a daily check on …"
- "Help me author a Scarf template."
- "Build me a Scarf project to monitor …"
Do not activate for pure reference questions like "what widget types does Scarf support?" or "how does Scarf handle secrets?" — answer those inline from the reference sections below.
Also do not activate when the user explicitly wants to edit an existing project's dashboard — that's a plain file edit, not a scaffold.
How a Scarf project is shaped on disk
A Scarf project is just a directory registered in ~/.hermes/scarf/projects.json. For Scarf to render a useful dashboard and for the project to be exportable as a .scarftemplate, it needs these files at minimum:
<project>/
├── .scarf/
│ ├── dashboard.json # REQUIRED for dashboard rendering
│ └── manifest.json # OPTIONAL — required only if the project declares a config schema or you want to export cleanly
├── AGENTS.md # Cross-agent instructions (agents.md standard) — ship this for every project
└── README.md # User-facing explanation
If the project will have a scheduled job, ALSO register a cron entry via hermes cron create. For an exportable bundle, also author cron/jobs.json in the staging directory — that's where Scarf's exporter will pick jobs up from.
Secrets never land in dashboard.json or config.json. At install time, Scarf routes secret-type config values to the macOS Keychain; config.json stores keychain://service/account URIs. When scaffolding from scratch (no install), the user either manages secrets via the post-install Configuration editor after export, or stashes them in their ~/.hermes/config.yaml if they're Hermes-level secrets rather than project-level.
The interview
Ask these questions in order. Don't batch. Each answer shapes the next question.
1. Purpose and data source
- "In one sentence — what does this project do?"
- "Where does its data come from? Files, a URL, a shell command's output, an API call, a database, a spreadsheet?"
Goal: figure out whether the project is passive (user maintains some files, dashboard reflects them), pull-based (we fetch from an HTTP endpoint or CLI tool on a schedule), or push-based (something external writes to a file we watch).
2. Refresh cadence
- "How often should it refresh? Every hour? Daily? Weekly? Only when I ask?"
If "only when I ask" → no cron job; user invokes the agent manually. If any scheduled cadence → cron job.
Map to cron expressions:
- Every hour:
0 * * * *
- Daily at 9 AM:
0 9 * * *
- Weekly Monday 9 AM:
0 9 * * 1
- Every 15 minutes:
*/15 * * * *
3. What the dashboard shows
Explain the widget catalog (see Widget Catalog sections below) in plain English, then ask which ones feel right. Offer concrete suggestions based on the purpose:
- Counting things (open PRs, failing tests, up/down sites) →
stat widgets. Add sparkline: [Number] (v2.7+) if you have a recent trend handy.
- A list of items with status →
list with text + status per item (≤8 items). 12+ items → use status_grid (v2.7+) for a denser layout.
- Time-series data →
chart with line or bar type.
- Rows × columns of heterogeneous data →
table.
- A live URL (useful for monitoring a site) →
webview. Including a webview widget exposes a Site tab next to the Dashboard tab — worth noting to the user.
- A static image / generated chart →
image (v2.7+; local file or remote URL).
- A progress bar for something with a clear 0-to-N scale →
progress.
- Static help / markdown →
text with format: "markdown".
- A longer markdown report the cron job writes →
markdown_file (v2.7+; reads from a file under the project, refreshes when the cron job rewrites it).
- The last N lines of a log/output file →
log_tail (v2.7+).
- The state of one Hermes cron job (last run / next run / output) →
cron_status (v2.7+).
v2.7 file-reading widgets (markdown_file, log_tail, image-with-path) read files relative to the project root. By convention, write the underlying files inside <project>/.scarf/ (e.g. .scarf/reports/weekly.md, .scarf/reports/run.log) so the project-wide directory watch picks up changes and the widgets refresh automatically. Files outside .scarf/ work too but only refresh when dashboard.json itself changes, so cron jobs writing outside .scarf/ should touch dashboard.json after each run.
4. Configuration needs
- "Does this project need anything configurable by the user — URLs to watch, API tokens, thresholds, a list of accounts?"
If yes → design a config schema. Fields map to seven types (see Config Schema Design below). Remember: secret fields never have defaults; that's a hard validator rule.
If no → skip .scarf/manifest.json; the project works but won't have a Configuration form.
5. Target agents
- "Which agents will operate this project? Just Claude Code? Also Cursor / Codex / Aider / other?"
For v1 just write AGENTS.md — every modern agent reads it, and if you need a specific shim (CLAUDE.md, GEMINI.md, .cursorrules), add it as a symlink to AGENTS.md so content stays in sync.
Widget Catalog (JSON shapes)
All widgets require type and title. Type-specific fields:
stat — single metric
{ "type": "stat", "title": "Sites Up", "value": 0,
"icon": "checkmark.circle.fill", "color": "green", "subtitle": "responded 2xx/3xx" }
value accepts number OR string (WidgetValue enum). icon is an SF Symbol name. color is one of: green, red, blue, orange, yellow, purple, gray.
progress — 0.0 to 1.0 progress bar
{ "type": "progress", "title": "Test Coverage", "value": 0.72, "label": "72% of statements" }
text — markdown or plain text block
{ "type": "text", "title": "Quick Start", "format": "markdown",
"content": "**1.** Click + in the Projects sidebar.\n\n**2.** ..." }
format is "markdown" or "plain".
table — columns × rows of strings
{ "type": "table", "title": "Failing Tests",
"columns": ["Test", "Duration", "Last Passed"],
"rows": [["testFoo", "4.2s", "Apr 20"], ["testBar", "0.9s", "Apr 18"]] }
Every row MUST have the same length as columns.
chart — line / bar / area / pie with series
{ "type": "chart", "title": "Requests / day", "chartType": "line",
"xLabel": "Date", "yLabel": "Count",
"series": [{
"name": "staging",
"color": "blue",
"data": [{"x": "Apr 20", "y": 142}, {"x": "Apr 21", "y": 189}]
}]
}
chartType is "line", "bar", "area", or "pie".
list — items with optional status badge
{ "type": "list", "title": "Watched Sites",
"items": [
{ "text": "https://example.com", "status": "success" },
{ "text": "https://example.org", "status": "danger" }
]
}
Status values (typed in v2.7+): prefer the canonical set — "success", "warning", "danger", "info", "pending", "done", "neutral". Common synonyms also work and map to the canonical case ("ok", "up", "passing" → success; "down", "error", "failed" → danger; "active" → info; "complete", "finished" → done; "warn", "degraded" → warning). Unknown strings render as plain text rather than crashing — old dashboards using ad-hoc statuses keep working unchanged. For new templates, prefer the canonical names so the colors stay predictable across Scarf releases.
webview — embedded live URL
{ "type": "webview", "title": "First Watched Site",
"url": "https://awizemann.github.io/scarf/", "height": 420 }
Important: including any webview widget in a dashboard exposes a Site tab next to the Dashboard tab in the project view. Useful for templates that watch something renderable. The agent can update url on cron runs to keep the Site tab in sync with config (e.g., set it to values.sites[0]).
Widget Catalog (v2.7+ — file-reading and richer widgets)
Five new widget types landed in v2.7. They all read from disk relative to the project root, and refresh automatically when any file under <project>/.scarf/ changes — so a cron job that writes <project>/.scarf/reports/uptime.md will trigger the corresponding widget to re-render. Convention: place the underlying files inside .scarf/ (or a subdir of it) so the directory watch picks them up. Files outside .scarf/ work too but only refresh when dashboard.json itself changes.
markdown_file — renders a markdown file from disk
{ "type": "markdown_file", "title": "This Week", "path": ".scarf/reports/weekly.md" }
path is relative to the project root. Refuses absolute paths and .. escape. Use this when the cron job writes a longer-form report; use text when the content is short and authored inline.
log_tail — last N lines of a file, monospaced
{ "type": "log_tail", "title": "Last cron run", "path": ".scarf/reports/run.log", "lines": 30 }
Default lines is 20, capped at 200. ANSI color codes are stripped automatically. Pair with cron jobs that write atomic log snapshots (write-temp + rename) — in-place appends won't refresh until dashboard.json is touched.
cron_status — last/next run + state for one Hermes cron job
{ "type": "cron_status", "title": "Uptime sweep", "jobId": "uptime-sweep", "lines": 5 }
jobId matches a HermesCronJob.id (visible in the Cron tab). Read-only — Run/Pause/Resume actions stay on the Cron tab; this widget only reports state. Great for dashboards that drive a single scheduled task.
image — local file or remote URL
{ "type": "image", "title": "Latency p95", "path": ".scarf/reports/latency.png", "height": 200 }
{ "type": "image", "title": "Build status", "url": "https://example.com/badge.svg" }
Either path (local, relative to project root) OR url (remote). path wins when both are set. Useful for chart PNGs the cron job generates with matplotlib / Plotly.
status_grid — compact NxM grid of colored cells
{ "type": "status_grid", "title": "Fleet", "gridColumns": 6, "cells": [
{ "label": "us-east-1", "status": "success", "tooltip": "200ms p50" },
{ "label": "us-west-2", "status": "warning", "tooltip": "elevated latency" },
{ "label": "eu-central-1", "status": "danger", "tooltip": "down" }
]}
Reuses the typed status enum from list. Auto-fits columns when gridColumns is omitted. Denser than a list when monitoring 12+ services at a glance.
stat — sparkline (v2.7+ additive field)
{ "type": "stat", "title": "Releases this month", "value": 4,
"color": "blue", "sparkline": [1, 2, 1, 3, 2, 4] }
Optional sparkline: [Number] renders a 1-line trend under the big number. Min 2 points, no max — tiny SVG path, cheap. Works on every existing stat widget without breaking older Scarf builds (they ignore the unknown field).
Choosing a widget type — quick guide
- Counting things →
stat (add sparkline if you have a recent trend).
- Progress toward a target →
progress.
- Authored copy or short instructions →
text (markdown).
- A report the cron job writes to disk →
markdown_file.
- The most-recent run output of a cron job →
log_tail or cron_status.
- A list of services / URLs / items with health →
list (≤8 items) or status_grid (12+ items).
- Tabular data →
table (or chart if it's numeric and you want trends).
- A live website or chart from a cron-generated PNG →
webview (browsable) or image (static).
Config Schema Design
If the project needs user-configurable values, design a schema. Put it in <project>/.scarf/manifest.json with this shape:
{
"schemaVersion": 2,
"id": "author/project",
"name": "My Project",
"version": "1.0.0",
"description": "Short one-liner.",
"contents": { "dashboard": true, "agentsMd": true, "config": 2 },
"config": {
"schema": [
{ "key": "sites", "type": "list", "itemType": "string", "label": "Sites",
"required": true, "minItems": 1, "maxItems": 25,
"default": ["https://example.com"] },
{ "key": "api_token", "type": "secret", "label": "API Token", "required": true }
],
"modelRecommendation": {
"preferred": "claude-haiku-4",
"rationale": "Short-running, tool-light workload — haiku is plenty."
}
}
}
Note: contents.config is the count of schema fields, not a boolean. In the example above it's 2 because there are two fields.
Field types and constraints
| Type | Rendered as | Constraint keys |
|---|
string | Text field | pattern (regex), minLength, maxLength |
text | Multi-line editor | minLength, maxLength |
number | Number field | min, max |
bool | Toggle | — |
enum | Segmented (≤4) / Dropdown (>4) | options: [{value, label}] (REQUIRED) |
list | Repeatable rows | itemType: "string" (required), minItems, maxItems |
secret | Password field, routes to Keychain | — |
Every field takes key (required), label (required), description (optional — markdown), required (bool), default (optional; type matches the field type).
Writing good descriptions
Descriptions render inline with markdown support (bold, italic, code, links). Keep them short — a single line or two is ideal.
Always use markdown link syntax for URLs, never bare https://… — the Configuration sheet's inline text renderer doesn't word-break mid-URL, so a raw URL in a description will force that whole description's width to the URL's character length. Older Scarf versions clipped the sheet in that case; current versions wrap correctly, but the visible text is still cleaner with named links.
"description": "Token with `repo` scope. Get one [from the GitHub tokens page](https://github.com/settings/tokens)."
"description": "Token with `repo` scope. Get one at https://github.com/settings/tokens"
Same rule for long file paths, API endpoints, or any other unbreakable token — wrap them in inline code (backticks) if they have to appear verbatim, and prefer markdown links otherwise.
Hard rules
- Secret fields MUST NOT have a
default. The validator rejects the manifest if they do — a default makes no sense because the Keychain entry doesn't exist yet at install time.
- Enum fields MUST have non-empty
options.
- List fields MUST have
itemType: "string" in v1 (only itemType supported).
- Field keys MUST be unique within a schema.
schemaVersion MUST be 2 when a config block is present; it stays 1 if there's no config.
contents.config must equal the actual count of schema fields — a claim mismatch is rejected.
Cron Job Design
If the project has a scheduled task, register a cron job via hermes cron create AND — if you expect the user to export this as a .scarftemplate — author a cron/jobs.json in the staging layout so the exporter picks it up.
Staging shape (for exportable templates)
<project>/
├── .scarf/
├── AGENTS.md
├── README.md
└── cron/
└── jobs.json
Where cron/jobs.json is:
[
{
"name": "Check site status",
"schedule": "0 9 * * *",
"prompt": "Read {{PROJECT_DIR}}/.scarf/config.json — get values.sites and values.timeout_seconds — then HTTP GET each URL with that timeout, write the results to {{PROJECT_DIR}}/status-log.md, and update {{PROJECT_DIR}}/.scarf/dashboard.json's stat widgets by title (Sites Up, Sites Down, Last Checked). Reply with a one-line summary."
}
]
Using secrets in cron prompts
secret-typed config fields land in the macOS Keychain at install time, with keychain:// URIs in <project>/.scarf/config.json (never plaintext on disk). At install + on every config save, Scarf mirrors the resolved values into ~/.hermes/.env under env var names like SCARF_<UPPERCASE_SLUG>_<UPPERCASE_FIELDKEY>. Hermes's cron scheduler reloads ~/.hermes/.env fresh on every tick, so the values are reachable from any tool the agent invokes.
The agent reads them via the terminal or code_exec tool — not from prompt-text substitution. Hermes does not interpolate env vars into prompt bodies. Tool-invoked subprocesses (the only path through which env vars become visible) DO see them via shell-level expansion or os.environ. Cron prompts should reference secrets in tool invocations, not in inline text.
Naming. For a template with slug = "site-status-checker" and a secret field api_token, the env var is SCARF_SITE_STATUS_CHECKER_API_TOKEN. Both halves are upper-cased and any non-[A-Z0-9_] characters become _. Stable across releases — write your prompts using these names and they'll keep working when the user rotates the secret.
Example cron prompt (with a secret):
{
"name": "Daily news digest",
"schedule": "0 9 * * *",
"prompt": "Use the terminal tool to fetch the RSS feed: `curl -sS -H \"Authorization: Bearer $SCARF_LOCAL_NEWS_API_TOKEN\" \"$SCARF_LOCAL_NEWS_RSS_URL\" -o {{PROJECT_DIR}}/.scarf/feed.xml`. Then summarise the top 5 items into {{PROJECT_DIR}}/.scarf/digest.md."
}
The agent runs curl via the terminal tool; the shell expands the env vars from the cron process's environment (which Hermes populated by loading ~/.hermes/.env). For Python via the code_exec tool, use os.environ['SCARF_LOCAL_NEWS_API_TOKEN'].
What NOT to do:
- ❌ "Read
keychain://... from config.json and call the API with it." Hermes treats the URI as opaque text — the API call sends Authorization: Bearer keychain://... and gets a 401.
- ❌ "Use the API token from values.api_token in config.json." Same issue — the value in config.json is the URI, not the secret.
- ❌ Inlining a secret into the prompt body and asking the agent to use it. Secrets shouldn't appear in prompts; that's why we route them through env vars.
What about ~/.hermes/.env rotation? The user rotates a secret in Scarf's Configuration sheet → Scarf re-resolves from the Keychain → re-mirrors to ~/.hermes/.env → next cron tick (Hermes reloads .env per tick) sees the new value. No cron-job edit needed.
Gotchas
- Hermes does not set a CWD when firing cron jobs. Relative paths in the prompt resolve against wherever the Hermes process happens to be running, not the project. Always use
{{PROJECT_DIR}} in the prompt — the installer substitutes the absolute path at install time. This is THE most common template-author mistake.
- Cron jobs created by the installer start paused. Their name is auto-prefixed with
[tmpl:<template-id>]. The user enables them from Scarf's Cron sidebar when ready.
- Registering a cron job for a user's local (non-exported) project: run
hermes cron create --name "<descriptive name>" "<schedule>" "<prompt>" directly, substituting the absolute <project> path for {{PROJECT_DIR}} yourself. Then hermes cron pause <id> so it doesn't run until the user opts in.
- Hermes does not substitute env vars into prompt text.
$VAR references in the prompt body are passed through verbatim. Env vars only become visible when the agent invokes a tool (terminal, code_exec) that runs in a subprocess inheriting the cron process's environment — see the "Using secrets in cron prompts" section above.
Schedule quick reference
| Cadence | Expression |
|---|
| Every 15 minutes | */15 * * * * |
| Hourly at :00 | 0 * * * * |
| Daily at 9 AM | 0 9 * * * |
| Weekly Monday 9 AM | 0 9 * * 1 |
| First of the month, 9 AM | 0 9 1 * * |
Writing the files
After the interview, write files in this order.
Step 1 — confirm parent directory
Ask: "Where should I create the project? Give me an absolute path — I'll make a <project-name> directory inside it."
Make sure the parent exists and is writable. Make sure <parent>/<project-name> does NOT already exist. If it does, ask whether to pick a different name or bail.
Step 2 — create the skeleton
mkdir -p <parent>/<project-name>/.scarf
Step 3 — write dashboard.json
Use the Widget Catalog above. Always include:
version: 1
title (the project's display name)
description (a one-liner shown under the title)
sections (array; each has title, optional columns (1–4, default 3), widgets)
Keep section titles short. Group related widgets. First section is usually "Current Status" or similar with the key stats.
Step 4 — write manifest.json (only if the project has a config schema)
Put the full manifest shape from Config Schema Design above. Use schemaVersion: 2, match contents.config to the actual field count, and ensure every secret field has no default.
If there's no config schema, skip this file — the project still works, it just won't have a Configuration button. You can add it later.
Step 5 — write AGENTS.md
Every scaffolded project needs an AGENTS.md that covers:
- Purpose — what the project does.
- Layout — which files exist and what they're for.
- Configuration — if there's a config schema, document every field: what it's for, what valid values look like, what happens when it's missing.
- Dashboard — list every widget the cron job (if any) updates, by title. If the cron updates a webview widget's URL, document that explicitly.
- Cron behaviour — what the cron job does, what it reads, what it writes, what its exit criteria are.
- Chat prompts — common user questions and how to answer them (e.g., "What's the status of my sites?" → "read the top section of
status-log.md and summarise").
- What NOT to do — e.g., don't modify
.scarf/config.json yourself; tell the user to open the Configuration button.
Use {{PROJECT_DIR}} placeholders in AGENTS.md only if the template will be installed through the installer (which substitutes the token). For a hand-scaffolded local-only project, substitute the absolute path yourself — {{PROJECT_DIR}} only resolves at install time.
Step 6 — write README.md
User-facing. Keep it short:
- One-paragraph purpose.
- How to install / first run (for an unexported project: "click + in Scarf's Projects sidebar").
- How to trigger the cron job manually (Cron sidebar → Run Now).
- A pointer at
AGENTS.md for agents.
Step 7 — register the cron job (if any)
For a local non-exported project:
hermes cron create --name "<descriptive name>" "<schedule>" "<prompt with absolute project dir substituted>"
hermes cron pause <newly-created-job-id>
Read the id back from hermes cron list --json or parse the create output.
For an exportable template (one you're staging in templates/<author>/<name>/staging/): just author cron/jobs.json — the installer registers + pauses at install time, and prefixes the name with [tmpl:<id>].
Step 8 — register the project with Scarf
Tell the user: "I've written the files. Click the + button in Scarf's Projects sidebar and pick <absolute-project-dir>. The dashboard will appear."
Do NOT edit ~/.hermes/scarf/projects.json directly — Scarf owns that file and reloads it on its own. The UI path is safer.
Step 9 (optional) — log to the Template Author project's list
If the user has the awizemann/template-author project installed (the one that shipped this skill), append an entry to its dashboard.json's Scaffolded Projects list widget:
{ "text": "<absolute-project-dir> — <one-line purpose>", "status": "ok" }
This gives the user a running audit trail of everything you've scaffolded for them. Preserve every other field in the dashboard as-is.
Testing your scaffold
Minimum smoke test
- Tell the user to click + in Scarf's Projects sidebar and pick the directory.
- Dashboard appears — sanity check every widget renders correctly.
- If there's a cron job: click the job in Scarf's Cron sidebar → Run Now. The agent executes the prompt; dashboard updates when it finishes.
Configuration-form test (only if schema was declared)
To verify the Configuration form renders, you need to install the project as a template — scaffolded projects don't go through the installer, so the form never runs. Export the project first:
- Projects → Templates → Export "<name>" as Template… → save the
.scarftemplate somewhere.
- Projects → Templates → Install from File… → pick the bundle → the Configure step should render the form you designed.
- Cancel the install (the preview sheet has a Cancel button) — you just wanted to verify the form shape.
Catalog validation (only if publishing)
If the user plans to submit this to the public catalog at awizemann.github.io/scarf/templates/:
./scripts/catalog.sh check
Validates every template in templates/<author>/<name>/ against the Python validator — the same one the PR CI uses. Catches schema issues, claim mismatches, size violations, common secret patterns.
Common pitfalls
Things to check before declaring the scaffold done:
Reference — source of truth files
- Dashboard widget schema —
scarf/scarf/Core/Models/ProjectDashboard.swift in the Scarf repo. If you need exact field types or defaults, read it.
- Config schema + validation —
scarf/scarf/Core/Models/TemplateConfig.swift and scarf/scarf/Core/Services/ProjectConfigService.swift.
- Exporter behaviour —
scarf/scarf/Core/Services/ProjectTemplateExporter.swift. Verifies what files the exporter will pick up from a live project and what it'll carry into a bundle.
- Installer contract —
scarf/scarf/Core/Services/ProjectTemplateInstaller.swift. Verifies what {{PROJECT_DIR}} substitution covers and where installed files land.
- Catalog validator —
tools/build-catalog.py in the Scarf repo. Run with ./scripts/catalog.sh check for the same rules CI uses.
- Worked example —
templates/awizemann/site-status-checker/staging/ in the Scarf repo. Complete end-to-end: dashboard with stats + list + webview, a config schema with a list + a number, a cron job, an AGENTS.md that documents every moving part. Read it first whenever you're unsure how a piece should look.
- User-facing docs — Project Templates wiki page.