AGENT SKILL

garden-plugin-author

Create, test, and publish plugins for the Digital Garden Eleventy template (oleeskild/digitalgarden, used by the obsidian-digital-garden plugin and Forestry.md). Use when asked to build a garden plugin, add a feature to a digital garden as a plugin, write or edit a garden-plugin.json manifest, or publish a plugin to the community registry.

License 许可
MIT
所属仓库
aj-gman/cyberpunk
最近更新
2026-11-09
Stars
★ 0
Forks
0

01 前言 Front Matter

name
garden-plugin-author
description
Create, test, and publish plugins for the Digital Garden Eleventy template (oleeskild/digitalgarden, used by the obsidian-digital-garden plugin and Forestry.md). Use when asked to build a garden plugin, add a feature to a digital garden as a plugin, write or edit a garden-plugin.json manifest, or publish a plugin to the community registry.

03 SKILL.md 正文

Authoring a Digital Garden plugin

A garden plugin is a directory that extends a Digital Garden site: markup injected into named layout slots, site-wide styles/scripts, and build-time Eleventy/markdown-it hooks. Plugins are distributed as public GitHub repos with a garden-plugin.json manifest at the repo root, and installed into src/plugins/<id>/ of a garden repo. Follow this skill end to end; do not invent extension points that are not listed here.

Where to work

  • Inside a garden or template checkout (a repo with .eleventy.js and src/site/): create your plugin directly at src/plugins/<id>/ and it runs immediately.
  • Standalone plugin repo: develop the plugin as its own directory, and for testing clone https://github.com/oleeskild/digitalgarden, run npm install, and symlink or copy your plugin into src/plugins/<id>/.

The plugin id must match ^[a-z0-9][a-z0-9-]*$, must equal the directory name, and must NOT start with dg- (reserved for first-party plugins).

Minimal viable plugin

src/plugins/reading-time/
  garden-plugin.json
  templates/badge.njk
{
  "id": "reading-time",
  "name": "Reading Time",
  "version": "1.0.0",
  "description": "Shows an estimated reading time under the note title.",
  "author": "Your Name",
  "slots": { "notes.header": "templates/badge.njk" },
  "noteSettings": ["dgReadingTime"]
}
{% if settings.dgReadingTime === true %}
<div class="reading-time">{{ ((content | striptags).split(" ").length / 200) | round }} min read</div>
{% endif %}

id, name, version, description, author are required. Everything else is optional. All declared paths must be relative, use /, and contain no .. — a violation makes the loader (and installers) reject the plugin.

Slots

Slot templates are plain Nunjucks fragments (no front matter). They are rendered with the full Eleventy data cascade — meta, settings, title, tags, created, updated, content, collections, graph, filetree, page — plus pluginSettings (this plugin's resolved settings). Always self-gate on your own setting: every enabled plugin's slots render unconditionally.

Slot Renders
common.head / notes.head / index.head in <head> (all pages / notes / home page)
common.header / notes.header / index.header in <header> after title and tags
common.beforeContent / notes.beforeContent / index.beforeContent in <main> before the content
common.afterContent / notes.afterContent / index.afterContent in <main> after the content
common.footer / notes.footer / index.footer end of <body> (scripts, overlays, tooltips)
navbar.actions navbar, where the search button sits
filetree.actions filetree sidebar below the site title
filetree.beforeTitle / filetree.afterTitle around the filetree site title
sidebar.top / sidebar.bottom right-hand sidebar
floating.bottomRight fixed stack in the bottom-right corner, filled from the corner upward; core's page panel toggle sits nearest the corner. Render one element per template (a button or link), it becomes a flex item and stacks above the previous one

Manifest form: "slots": { "<slot>": "file.njk" } or a list of files. Use common.footer for anything with a <script> or overlay markup.

Render order within a slot is the site owner's call, not the plugin's: plugins render by the order number on their entry in src/plugins/plugins.json (lower first, default 0), then by id. In floating.bottomRight a lower order sits nearer the corner. Example: {"plugins": {"theme-switcher": {"order": 10}, "random-note": {"order": 20}}}.

Regions (exclusive replacement)

A region replaces a core UI area instead of adding to it — at most one enabled plugin provides each region (first by id wins; conflicts warn). Currently: navigation (default: the core navbar; the shipped dg-filetree plugin claims it to render the folder-tree sidebar).

"regions": { "navigation": "templates/my-nav.njk" }

A custom navigation plugin gets the full data cascade, including the core-computed filetree data structure, and may include core components (e.g. fall back to {% include "components/navbar.njk" %}) and render sub-slots via components/pluginSlot.njk. Users switch navigations by disabling dg-filetree and enabling yours. Read src/plugins/dg-filetree/ in the template as the reference.

A navigation plugin MUST render the surfaces other extensions rely on, in its own markup: the navbar.actions and filetree.actions plugin slots (the search button lives there), and the site owner's custom filetree components — {% for imp in dynamics.filetree.beforeTitle %}{% include imp %}{% endfor %} plus the same for dynamics.filetree.afterTitle. dg-filetree does both; copy its template as the starting point.

Build hooks ("hooks": "index.js")

module.exports = {
  // Both optional. context = { settings, manifest, pluginDir }
  setupMarkdown(md, context) { md.use(require("markdown-it-footnote")); },
  setupEleventy(eleventyConfig, context) {
    eleventyConfig.addFilter("myFilter", (v) => v);
    // To emit a page (needs collections), register a virtual template:
    // eleventyConfig.addTemplate("my-page.njk",
    //   require("fs").readFileSync(require("path").join(context.pluginDir, "templates/page.njk"), "utf8"),
    //   { permalink: "/my-page.json", eleventyExcludeFromCollections: true });
  },
};

Rules: hooks run at config time in Node; they may only require packages the template already ships (check the template's package.json) or pure JS files vendored inside the plugin dir — plugins cannot add npm dependencies. Hook changes need a dev-server restart. Thrown errors are caught and the hook is skipped with a [plugins] warning.

Settings and per-note flags

"settings": [
  { "key": "label", "name": "Label", "description": "…",
    "type": "text",              // text | boolean | number | select
    "default": "Hello",
    "options": ["a", "b"],       // select only
    "env": "MY_ENV_VAR" }        // optional env fallback
],
"noteSettings": ["dgMyFlag"]
  • Setting resolution: stored value in src/plugins/plugins.json → env var (env or the key) from the garden's .env → default. Read them as pluginSettings.<key> in templates, context.settings.<key> in hooks.
  • noteSettings keys (camelCase, dg-prefixed by convention) resolve like core note settings: env var of the same name is the global default, a note's frontmatter dg-my-flag: true/false overrides per note. Read as settings.<key> in templates.

Styles, scripts, assets

  • "styles": ["styles/x.scss"] — .scss compiled per file (_partials skipped) to /plugins/<id>/styles/x.css and linked in <head>; plain .css copied and linked as-is. Prefer the garden's --dg-* CSS variables for colors/spacing so themes keep working.
  • "scripts": ["assets/client.js"] — copied and loaded via <script defer> in <head>.
  • "assets": ["assets/"] — copied to /plugins/<id>/…, referenced only by your own code.

Test before publishing

From the template/garden checkout:

  1. npm install once, then npm run dev — slot template, style, and plugins.json edits hot-reload; restart after hook or manifest changes.
  2. Watch for [plugins] … warnings in the build log — each one is a manifest or runtime problem in your plugin.
  3. Build once with the plugin disabled (src/plugins/plugins.json → {"plugins": {"<id>": {"enabled": false}}}) and confirm the site is unchanged.
  4. If the template has a test suite, npm test must stay green.

Publish

  1. Put the plugin at the root of a public GitHub repo (garden-plugin.json at top level). Suggested repo name: garden-plugin-<id>.
  2. Add a README (what it does, a screenshot, settings) and a screenshot file — the community directory shows it.
  3. Tag a release matching the manifest version: git tag v1.0.0 && git push --tags. Installers fetch the latest release tag, falling back to the default branch only when no release exists. Every future change: bump version, tag again.
  4. Users can now install it by pasting the repo URL, or manually by copying the directory into src/plugins/<id>/.
  5. To list it in the community directory, open a PR against oleeskild/digitalgarden-plugins adding one entry to community-plugins.json: { "id": "<id>", "name": "…", "author": "…", "description": "…", "repo": "you/garden-plugin-<id>", "screenshot": "screenshot.png" }.

Hard rules

  • Never require install-time execution: no postinstall steps, no "run this script to finish setup". Installation is file copying only.
  • A plugin must never break the build when misconfigured — the loader warns and skips, but keep your own code defensive too.
  • Don't touch files outside your plugin directory or claim another plugin's id. Don't use the dg- prefix.
  • Plugin code runs in the site build and in visitors' browsers — keep it small, readable, and dependency-light so users can audit it.

For deeper reference, read the first-party plugins under src/plugins/ in the template repo — dg-link-preview is the smallest, dg-search the most complete, and dg-filetree the navigation reference.

04 同仓库更多 Skills

aj-gman
aj-gman/cyberpunk

obsidian-markdown

Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.

文档处理
★ 0
aj-gman
aj-gman/cyberpunk

obsidian-cli

Interact with Obsidian vaults using the Obsidian CLI to read, create, search, and manage notes, tasks, properties, and more. Also supports plugin and theme development with commands to reload plugins, run JavaScript, capture errors, take screenshots, and inspect the DOM. Use when the user asks to interact with their Obsidian vault, manage notes, search vault content, perform vault operations from the command line, or develop and debug Obsidian plugins and themes.

开发工程师
★ 0
aj-gman
aj-gman/cyberpunk

obsidian-bases

Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.

★ 0
aj-gman
aj-gman/cyberpunk

json-canvas

Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.

视觉设计
★ 0
aj-gman
aj-gman/cyberpunk

digitalgarden

AI is knowledgeable about the inner workings of the Digital Garden plugin for Obsidian

★ 0
aj-gman
aj-gman/cyberpunk

defuddle

Extract clean markdown content from web pages using Defuddle CLI, removing clutter and navigation to save tokens. Use instead of WebFetch when the user provides a URL to read or analyze, for online documentation, articles, blog posts, or any standard web page. Do NOT use for URLs ending in .md — those are already markdown, use WebFetch directly.

内容创作者
★ 0