| name | linkyee-plugin-builder |
|---|---|
| description | Use when the user wants to add dynamic data (GitHub stars, latest blog posts, weather, follower counts, repo activity, anything fetched from a URL) to their linkyee site by writing a build-time plugin. Triggers on phrases like "add a plugin", "show my latest Medium post on the page", "fetch X and display it", "make linkyee pull data from Y", "inject a value into the page". Generates `plugins/<PluginName>.rb`, wires it up in `config.yml`, references it in the right Liquid spot, and runs the build to verify. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
You help users add build-time plugins to linkyee — a Ruby/Liquid
static-site generator. A plugin is a small Ruby class that fetches some
data at build time and exposes it to the templates as vars.<PluginName>.
The canonical reference for the plugin contract lives at
plugins/README.md. Read it before you
write code; it contains examples, helpers, and conventions that this
skill expects you to follow. Do not duplicate that information here —
this skill is the workflow, the wiki is the spec.
config.yml lists enabled plugins. At build time scaffold.rb
instantiates <PluginName>.new(yaml_values), calls execute(), and
stores the return value at settings["vars"][<PluginName>]. Liquid then
renders {{ vars.<PluginName> }} (and friends) inside config.yml
strings and the theme's index.html. If a plugin raises, scaffold logs
the error and sets the value to nil — the build still completes.
Follow these steps in order. Don't skip steps; the verification step in particular catches most mistakes.
Before writing anything, confirm:
- What data does the user want? (a star count, a list of posts, a weather temp, a follower count, the latest commit, …)
- From where? (specific URL, RSS feed, JSON API, scraped HTML)
- Where on the page should it appear? (footer, a link's text, a new
link, a new section in
index.html)
If anything is ambiguous, ask one focused question. Don't ask three clarifying questions at once.
Read plugins/README.md § "Built-in plugins". If GithubRepoStarsCountPlugin,
GithubLastCommitPlugin, GithubProfilePlugin, or RSSFeedPlugin
already does the job, don't write a new one — just enable and
reference it. Tell the user.
Run Read on config.yml to see how the user already structures their
links and what's already in vars. This affects naming and where to
inject the value.
Create plugins/<PluginName>.rb:
- Name the class to match the file. Use a descriptive
*Pluginsuffix (e.g.WeatherPlugin,MediumLatestPostsPlugin). require_relative 'Plugin'at the top.- Inherit from
Plugin. - Use the helpers (
http_get,http_get_json,args,params,cache,log) — do not callNet::HTTPorURIdirectly. They're already wrapped with redirect following, timeouts, and error handling. - Be defensive. Return a sensible default (
0,"",{},[]) on failure. Plugins must not raise. - Use String keys in returned hashes (Liquid can't look up symbol keys).
- Match the data shape on success and failure (don't return a Hash on
success and
nilon failure — Liquid breaks).
Look at the existing built-in plugins as templates — they're short and demonstrate the patterns.
Add to plugins: using the YAML style that matches the plugin's
arguments (list-style for args, hash-style for params). Then
reference {{ vars.<PluginName>… }} in the appropriate
links/socials/title/tagline/footer field, or edit the
theme's index.html if the user wants a loop / conditional.
Run:
bundle exec ruby ./scaffold.rbThen check:
- Build exits 0
- No
[<PluginName>]or[scaffold] Plugin '<PluginName>' failedlines in stderr - The injected value appears in
_output/index.html(grep for it)
If the value is missing, read the stderr output — the new
scaffold.rb swallows plugin failures but logs them clearly. Do not
guess; fix the underlying error.
If the plugin needs an API key, personal access token, OAuth token, or any other credential:
- Never put the secret value in
config.yml. That file is committed to git and rendered into the public site at build time. Anything in it is public, forever, including in old commits. - Never hardcode the secret in the plugin's
.rbfile. Same problem. - Always read it from
ENV:token = ENV["MEDIUM_TOKEN"] return [] if token.nil? || token.empty? http_get_json(url, headers: { "Authorization" => "Bearer #{token}" })
- Always document the secret name in the plugin's header comment so the user knows what to set.
- Always tell the user how to wire it through, in three steps:
- Repo secret: GitHub → repo → Settings → Secrets and variables
→ Actions → New repository secret. Name it (e.g.
MEDIUM_TOKEN), paste value. - Workflow env: edit
.github/workflows/build.yml, add anenv:block to theDeploystep:- name: Deploy env: MEDIUM_TOKEN: ${{ secrets.MEDIUM_TOKEN }} run: bash deploy.sh
- Local dev: same name, exported in shell:
export MEDIUM_TOKEN=xxx && bundle exec ruby ./scaffold.rb
- Repo secret: GitHub → repo → Settings → Secrets and variables
→ Actions → New repository secret. Name it (e.g.
If the user asks you to "just paste the token in config.yml for now", refuse and explain why. The secret will end up in git history and on the deployed gh-pages branch — no easy way to revoke once leaked.
Tell the user:
- What plugin was created
- What got added to
config.yml - Where in the rendered page the value now appears
- How to extend it (e.g. "add more entries to the list under
MyPlugin:")
- Don't add new gems to the
Gemfileunless absolutely required.nokogiri(already in Gemfile) plus stdlibnet/httpandjsoncover the vast majority of cases. - Don't scrape sites that have an obvious public JSON/Atom endpoint.
RSS / Atom (
/feed,/commits.atom) and JSON APIs are far more stable than HTML scraping. - Don't hardcode credentials. If a token is required, read it from
ENVand document the secret name in the plugin's header comment. - Don't add per-request side effects (writing files, sending
notifications).
executeshould be a pure data fetch. - Don't invoke things at build time that can take more than ~30s in total — the GitHub Actions build is on a free runner and we run it daily. Cap the work.
- Don't touch theme files unless the user asked for layout changes.
Most plugin requests are satisfied by editing
config.ymlonly. - Don't create a new plugin when an existing one (
RSSFeedPluginespecially) already handles the case.
- "Make it run in the visitor's browser instead." — linkyee is a static site; plugins are build-time only. If they want runtime data, they need separate JS in the theme — out of scope for this skill.
- "Have the plugin email me when X." — no side effects in plugins. Suggest a separate GitHub Actions workflow.