A directory of reusable agent skills. Each skill is a folder of Markdown and
code; the app renders every skill's SKILL.md and gives you a one-click Copy
skill button that puts a ready-to-run payload on your clipboard.
A skill is just a folder β drop skills/<slug>/SKILL.md in and it shows up.
There's also a Slack "Skills Librarian" bot (in librarian/) that can list,
explain, and CRUD skills by chat.
π Catalog: https://dcm31--92861134610211f1a0c51607ee4eb77e.web.val.run
A skill is a folder under skills/<slug>/ following the standard Agent-Skills
layout, so it stays valid and discoverable by agents reading it outside this
app:
skills/<slug>/
SKILL.md # entry point β what the skill does and the flow to follow
scripts/ # runnable code the agent writes locally
knowledge/ # reference docs the agent reads for context
assets/ # optional HTML fragment, shown as a live example
Only SKILL.md is required. Its YAML frontmatter is the skill's metadata.
---
name: PR Summarizer
description: Reads a GitHub PR diff and writes a release note. Use whenβ¦
category: Engineering
---
# PR Summarizer
β¦the instructions an agent followsβ¦
The catalog is derived at request time: the app lists skills/*, reads each
SKILL.md's frontmatter for name / description / category, and collects
the folder's files. Add a folder β it appears. Edit frontmatter β the card
updates. (name falls back to a title-cased slug, category to
"Uncategorized", if you omit them.)
Three ways, in increasing convenience:
- The Slack Skills Librarian (see below) β "list the skills", "how does X work", "create a skill thatβ¦", "change the UI skill toβ¦".
- A coding agent via the Val Town plugin (Claude Code / Codex / Cursor):
npx plugins add val-town/plugins, then ask it to add or edit a skill. - By hand in the Val Town editor β a skill's body is plain Markdown, so a typo fix is a two-second edit that deploys on save.
A Slack agent (Claude + the Vercel AI SDK, built on the templates/ai-slackbot
quickstart) that manages this library through a small set of structured tools.
The design split: reads are broad, writes are deliberate.
| Tool | What it does |
|---|---|
list_skills | Lightweight catalog (name / category / description) |
read_skill / read_skill_file | Read a skill's SKILL.md or a supporting file |
create_skill | New skill β writes the folder + SKILL.md with correct frontmatter |
update_skill | Change metadata, body, or one file of an existing skill |
delete_skill | Remove a skill (guarded by an explicit confirm) |
create_skill / update_skill own the folder-and-frontmatter shape, so the bot
can't produce a malformed skill, and every write is scoped to this val's
skills/ folder. Reads use std/utils (token-free); writes use the Val Town
REST API with a token (VALTOWN_TOKEN) and target the current branch, so on
the librarian branch new skills land on the branch until you merge to main.
- Open the bot's HTTP endpoint (the
librarian/events.tsfile's URL) and click Create Slack App β β scopes, events, and the webhook URL are pre-filled from a manifest. - Give the bot its face: Slack manifests can't carry an icon, so upload the
owl (served at the endpoint's
/icon.png) under the app's Basic Information β Display Information. Replies wear the owl automatically viachat:write.customizeeither way; the upload covers everything else (DM list, Assistant pane, app home). - Install the app to your workspace, then set these environment variables on
this val:
SLACK_BOT_TOKEN,SLACK_SIGNING_SECRETβ from the Slack appANTHROPIC_API_KEYβ for the modelSLACK_APP_ID(optional) β powers the catalog toolbar's "DM the librarian" link
- Authorize Val Town writes with one click on the
/connectpage (thelibrarian/connect.tsendpoint β also surfaced from the catalog's bottom bar). That stores a self-refreshing OAuth token so the librarian can create & edit skills. No API token to create or paste. - Message the bot in its Assistant tab or
@mentionit in a channel.
Prefer a token over OAuth (or running headless)? Set
VALTOWN_TOKENto a Val Town API token with val write access and the librarian uses that instead β it takes priority. Either way, only the skill-CRUD tools touch it.
A skill can ship a live preview β a working UI rendered right on its page. Drop
an HTML fragment in the skill's assets/ folder (e.g. assets/showcase.html)
and reference it from SKILL.md with a Markdown link. The app renders the first
assets/*.html it finds as an Examples panel, and serves it standalone
(iframe-embeddable) at /skills/<slug>/example. The ui-example skill ships
one and walks through the feature.
http.tsx # public catalog routes
lib/skills.ts # skill discovery (folder β catalog), file + copy-payload helpers, frontmatter parse
lib/vt.ts # Val Town REST client: skill writes + a catalog read-list fallback
og.ts # social card image
components/ # Layout, Home, SkillDetail, FileView, FileTree
frontend/client.js # clipboard wiring for [data-copy] buttons
skills/<slug>/ # the skills themselves (source of truth)
librarian/ # the Slack Skills Librarian bot
events.ts # HTTP entry: setup page + /icon.png + Slack /events + /process
generate-response.ts # the agent loop (Claude + tools)
icon.ts # the owl avatar (embedded base64 PNG)
skill-tools.ts # list / read / create / update / delete tools
slack-utils.ts # Slack client, verification, thread reconstruction
handle-*.ts # app_mention / message / thread_started handlers
prompt.md # librarian system prompt
| Route | Purpose |
|---|---|
/ | Home β grid of skill cards |
/skills/:slug | Skill detail β rendered SKILL.md, Copy button, file tree |
/skills/:slug/example | Standalone live example (when a skill ships an assets/*.html) |
/skills/:slug/files/* | Raw file viewer |
/client.js, /source, /og.png | Clipboard JS, source redirect, social card |
By default the catalog is public. To restrict it to a Val Town org, sign
requests through std/oauth and check the signed-in user against your org's
members (wrap the http.tsx fetch export with the OAuth middleware). Requires
a Teams account.