Skip to content

feat(skills): feed-backed community skills, and a feed field on the Add a skill form - #122

Merged
perminder-klair merged 3 commits into
mainfrom
feat/skill-feeds
Oct 8, 2026
Merged

perminder-klair merged 3 commits into
mainfrom
feat/skill-feeds

Conversation

@perminder-klair

@perminder-klair perminder-klair commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Merge after perminder-klair/subwave#1828 is in a release. Stations on an older controller would install these two skills without their feed: the brief alone, telling the DJ to report items it never received.

 buildSkills()
   slug, label, brief, cooldown, window, context
+  cron          # 5 fields, or 6 with seconds (controller's SKILL_CRON_RE)
+  cronOnly      # true/false only; refused without a cron
+  cohosts       # true/false only
+  feed          # https only
+  feedMaxItems  # 1–50, only alongside a feed
  • cron, cronOnly and cohosts were already read by the controller but dropped here, so a community cron: never reached a station. Each is now emitted, and an unusable value fails the build rather than silently installing a different skill.
  • feed is how a catalog skill gets real data without code. On install it becomes the skill's own feed: line, and the station's loader generates the fetch tool.
  • The Add a skill issue form takes a feed. It has optional Feed URL and Feed items to read inputs. The submission workflow validates them with the builder's rules and writes them into the generated SKILL.md. A generated PR for a feed skill opens with a "Reads a feed" note naming the URL for the reviewer. The station's Share to community button (feat(controller): let community catalog skills carry a feed perminder-klair/subwave#1828) prefills both inputs.
  • Two new skills:
skills/
├── music-press/   # Pitchfork news → one story: releases/tours/reunions; skips lawsuits/feuds; a death handled with care
└── good-news/     # Good News Network → one concrete good story; skips roundups/promos/opinion

Both speak only from fetched items and stay silent when nothing fits. The sources were picked by reading the live feeds: Consequence carries Prime Day deal posts, NME and Stereogum lean on controversy and opinion, and Positive News is mostly promos and essays.

Evidence

  • node scripts/build-catalog.mjs --check → ✓ catalog valid (skills=11 …). The only warning is Trevor's, which predates this PR. Emitted entries:
    { "slug": "music-press", "label": "Music press bulletin", "cooldown": "3h",
      "feed": "https://pitchfork.com/feed/feed-news/rss", "feedMaxItems": 10 }
  • Builder rules against a scratch copy with bad entries: all 7 refused, and a valid cron + cronOnly + cohosts entry passes.
    cohosts must be true or false (is "yes")
    feedMaxItems must be a whole number from 1 to 50 (is "99")
    cron "0 17 *" must be 5 fields, or 6 with seconds
    feed "http://example.com/rss" must be an https:// URL
    feed "not a url" is not a URL
    cronOnly needs a cron — without one the skill would never air
    feedMaxItems needs a feed
    
  • End to end, with #1828 and this branch's built catalog on an isolated station: installing music-press from the admin Community modal wrote feed: + feedMaxItems:. Its generated tool returned live Pitchfork headlines.
  • Form workflow: I ran the build-pr script locally from the YAML with a mocked GitHub API, using six issue bodies:
    feed-ok      https feed + 12 items → feed:/feedMaxItems: written, PR body flags the feed, builder accepts
    no-feed      → no feed lines, no flag (unchanged output)
    feed-http    → "Feed URL must start with https://"
    feed-space   → "Feed URL must be a single URL of up to 500 characters"
    count-alone  → "Feed items to read needs a Feed URL"
    count-big    → "Feed items to read must be a whole number from 1 to 50"
    
    Unquoted URLs with ?, &, #, ,, {} and ' all round-trip through the station's YAML frontmatter parser.
  • Not yet tested: the briefs have not been run through a model.

Merge Danger

Door: two-way

Removing the skills or reverting the builder takes effect on the next catalog rebuild. Stations that already installed a skill keep their copy, as with any catalog entry.

Blast Radius: catalog-wide

  • The builder now fails on cron/cronOnly/cohosts/feed values it used to ignore. No existing entry uses them, so the current catalog still builds.
  • A form-submitted feed makes every installing station fetch a contributor-chosen URL. It is https-only, flagged in the generated PR for review, and shown to operators before install.

The controller's CommunitySkill already reads cron/cronOnly/cohosts, but
build-catalog only emitted label/brief/cooldown/window/context, so a
`cron:` in a community SKILL.md was silently dropped on the way to every
station. The builder now emits them, plus `feed` and `feedMaxItems`, which
a station (with the matching controller change) installs as the skill's
own feed knob: real data for a skill, with no code shipped.

Each is validated rather than dropped:
- cron: 5 fields, or 6 with seconds (the controller's SKILL_CRON_RE)
- cronOnly / cohosts: true or false only
- cronOnly without cron: refused, since the skill would never air
- feed: https only; feedMaxItems: 1-50, and only alongside a feed
The first data-backed community skills. Each reads a public feed and
speaks only from what it returns, staying silent when nothing fits.

- music-press: one story from Pitchfork's news feed. Releases, tours,
  reunions and signings; skips lawsuits, feuds and politics; handles a
  death with care. Prefers a story about the artist on air.
- good-news: one concrete story from Good News Network. Skips roundups,
  anniversaries and opinion pieces.

Pitchfork and GNN were chosen after reading the live feeds: Consequence
mixes in shopping-deal posts, NME and Stereogum lean on controversy and
opinion, and Positive News is mostly promos and essays.
The issue form gains two optional inputs, Feed URL and Feed items to read,
so a feed-backed skill can be submitted without a hand-written PR. The
submission workflow validates them with the builder's rules (https only,
one URL with no whitespace and at most 500 chars, 1-50 items, items only
alongside a feed) and writes `feed:` / `feedMaxItems:` into the generated
SKILL.md. The validate job re-checks through build-catalog.mjs.

A generated PR for a feed skill opens with a "Reads a feed" note naming
the URL, so the reviewer checks it is public, stable and on-topic before
every installing station starts fetching it. The input ids match what the
station's Share to community button now sends, so a shared feed skill
arrives prefilled.
perminder-klair added a commit to perminder-klair/subwave that referenced this pull request Oct 7, 2026
The community add-skill form now has `feed` and `feed-max-items` inputs
(getsubwave/community#122), so Share to community passes the skill's own
feed knobs alongside its brief. GitHub prefills issue-form text inputs by
field id; a skill without a feed sends neither.
@perminder-klair perminder-klair changed the title feat(skills): feed-backed community skills (Music press bulletin, Good news) feat(skills): feed-backed community skills, and a feed field on the Add a skill form Oct 7, 2026
perminder-klair added a commit to perminder-klair/subwave that referenced this pull request Oct 8, 2026
* fix(controller): decode numeric character references in feed items

fast-xml-parser decodes only the five named XML entities, so WordPress
punctuation reached the DJ as literal text: `Man’s`, `Loads of
“Friends”`, `&`. stripHtml now decodes decimal and hex
references after `&`, so a doubly-encoded `’` resolves too.
An out-of-range reference is left as text rather than throwing out of the
parse. Affects every feed skill, the built-in News included.

* feat(controller): let community catalog skills carry a feed

Catalog skills are prompt-only by contract, so an installed one had no data
tool and could only write from its brief. A `feed:` is data, not code (#1616
generates the fetch tool from the URL), so a catalog entry may now declare
`feed` + `feedMaxItems`, and install writes them as the skill's own knobs.
The installed skill gets the same `skill_<name>` tool a hand-set feed does.

- registry: the feed URL goes through the loader's resolveFeedConfig. An
  entry whose declared feed that rule refuses is dropped whole, since a
  brief written to report fetched items must not install with nothing to
  fetch. A bad feedMaxItems costs only the count.
- install: writeSkillFile receives communitySkillConfig(cs); a prompt-only
  entry writes byte-identical SKILL.md.
- admin Community modal and public /skills card show "reads <host>", and
  the modal copy no longer claims the catalog ships with the image.

Pinned by scripts/community-skill-feed.test.ts (catalog + feed on loopback,
install over HTTP, generated tool fetched); verified failing with the
install line reverted.

* feat(web): prefill the feed when sharing a skill to the community

The community add-skill form now has `feed` and `feed-max-items` inputs
(getsubwave/community#122), so Share to community passes the skill's own
feed knobs alongside its brief. GitHub prefills issue-form text inputs by
field id; a skill without a feed sends neither.
@perminder-klair
perminder-klair merged commit 27c9d56 into main Oct 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant