MANUAL · 06
Custom skills.
The things the DJ does between tracks (a weather check, a headline, a traffic gag) are skills. Seven ship built in, and you can edit any of them or add your own by dropping a folder into state/skills, no code changes to the station.
WHAT A SKILL IS
One thing: a between-track line.
A SUB/WAVE skill is a single between-track spoken segment — the DJ glances at something, then either says one short line over the music or stays quiet. The format borrows from Anthropic’s skills (a SKILL.md with YAML frontmatter and a markdown body, plus optional code), but the meaning is narrower. These don’t process documents or run tasks; they decide what the DJ says next.
THE LAYOUT
A folder under state/skills.
Drop a folder into state/skills/. It needs a SKILL.md; an optional tool.mjs lets the segment look at live data before the DJ speaks.
state/skills/
moon-phase/
SKILL.md # frontmatter (→ settings) + body (→ the DJ's brief)
tool.mjs # OPTIONAL: a data fetcher the DJ can callA ready-to-copy example ships in the repo at docs/examples/skills/moon-phase. Copy it into state/skills/ and hit Rescan on the admin Skills page.
SKILL.md
Frontmatter, then the brief.
The frontmatter sets the skill’s metadata; the markdown body is the brief the DJ follows: what to say, in what tone, and when to stay silent. Only a non-empty body is required; every key has a sensible default.
---
name: moon-phase # the slug (defaults to the folder name)
label: Moon phase # label shown in admin (defaults to a title-cased name)
cooldown: 6h # min gap between auto firings — "90m" | "6h" | "2d" | "45" (minutes)
window: any # "any" (default) | "commute" — commute hours only
requiresKey: SOME_API_KEY # OPTIONAL: env var the skill needs; unset → stays inert
---
If tonight's moon is at a notable phase, work it into one short, in-character
line, the way a late-night presenter might glance out the window. Skip it when
the phase is unremarkable.For a new skill the name must be a lowercase slug that isn’t a built-in kind; naming a folder after a built-inedits that one instead (see below). Bad frontmatter is logged and skipped, and never crashes the station.
EDITING THE BUILT-INS
The shipped skills are files too.
The seven built-ins — weather, news, traffic, curiosity, album anniversaries, library deep-cuts, and web search — are written into state/skills/<kind>/SKILL.md the first time the station boots. Editing one (on the admin Skills page, or the file directly) overrides its brief, cooldown, or label in place. A built-in file may leave the body empty to keep the default wording, and never loads a tool.mjs; the built-ins already have their data wired in.
The big one: News reads the BBC by default. Hit Edit on the News skill, paste your own RSS feed (any RSS 2.0 feed, though not Atom yet) and rewrite the brief in your station’s voice, then Save. It’s live on the next break, no restart.
---
name: news
label: News headlines
cooldown: 45m
feed: https://feeds.npr.org/1001/rss.xml # any RSS 2.0 feed
feedMaxItems: 10
---
One fresh headline in a single sentence — in the station's voice,
not a newsreader's. Skip anything dull or stale; silence is fine.The NEWS_FEED_URL environment variable only seeds this file on the very first boot — after that the file (or the admin form) wins.
tool.mjs — OPTIONAL
Let the DJ look before it speaks.
With a tool.mjs, the DJ can fetch live data before deciding whether to air the line, the same mechanism the built-in weather and news skills use. Export a default function; return any JSON, and use { available: false } to tell the DJ there’s nothing worth airing.
export default async function (ctx, state) {
// ctx — the moment: { time, weather, festival, dominantMood, clock }
// state — cross-tick memory (persists between firings)
return { available: true, phase: 'full moon', illumination: 100 };
}The call is timeout-guarded and any error degrades cleanly to “no data”; a slow or broken skill can never hang the station. With no tool.mjs, the skill writes from its brief alone (like the built-in traffic gag).
tool.mjs executes inside the controller, the same trust model as installing a local tool. Only drop in code you’ve read and trust.
GOING LIVE
Discovered, then enabled by you.
A freshly dropped skill appears on the admin Skills page toggled off. It can’t air (by itself or via the DJ) until you enable it there. Dropping a folder never puts unreviewed content (or code) on air.
Skills load at boot, and on demand via the Rescan state/skills button on that page, which picks up new folders and edits to SKILL.md / tool.mjs without a restart. Like the built-ins, a custom skill only fires autonomously when it’s enabled and assigned to the persona on air (Personas page). Run now is an operator override that ignores the toggle, the persona, the frequency gate, and the cooldown.
Full reference, including the example skill, lives in docs/custom-skills.md.