Resource Schema

Defines two file shapes the catalog produces:

  1. index.json — single lightweight catalog at the repo root, fetched by the in-app Community panel.
  2. manifest.toml — one per resources/<id>/<version>/ folder, full detail for a single resource.

The index is a cache of the manifests. The manifest is the source of truth; the index is regenerated from manifests by scripts/build-index.js.


index.json

{
  "schemaVersion": 1,
  "generatedAt": "2026-05-20T00:00:00Z",
  "resources": [
    {
      "id": "summarize-skill",
      "type": "skill",
      "version": "0.1.0",
      "name": "Summarize",
      "author": "Undra",
      "description": "An AI skill that condenses long documents into a tight summary.",
      "license": "CC0-1.0",
      "tags": ["ai", "writing"],
      "screenshots": [],
      "verified": true,
      "downloads": 0,
      "manifestUrl": "https://community.undra.com/resources/summarize-skill/0.1.0/manifest.toml",
      "payloadBaseUrl": "https://community.undra.com/resources/summarize-skill/0.1.0/"
    }
  ]
}

Fields

Field Type Required Notes
schemaVersion int yes Bump when the index shape changes incompatibly.
generatedAt ISO-8601 string yes Set by build-index.js at generation time.
resources array yes One entry per (id, version) pair — multiple versions of the same id may coexist.
resources[].id string yes Kebab-case, globally unique.
resources[].type enum yes "skill" | "extension" | "font" | "agent"
resources[].version semver string yes MAJOR.MINOR.PATCH.
resources[].name string yes Display name.
resources[].author string yes Display author.
resources[].description string yes One-line description (≤ 200 chars).
resources[].license SPDX string yes E.g. "MIT", "CC0-1.0", "OFL-1.1".
resources[].tags string[] no Free-form, lowercase.
resources[].screenshots string[] no URLs (absolute or payloadBaseUrl-relative).
resources[].verified bool no True for curator-reviewed entries in this repo.
resources[].downloads int no Static for v1; placeholder for future analytics.
resources[].manifestUrl URL yes Absolute URL to the full manifest.
resources[].payloadBaseUrl URL yes Absolute URL prefix for resolving payload files. Trailing slash required.
resources[].payload object yes Full per-type payload table from the manifest (e.g. { skill: { entry: "skill.md" } }). Embedded so installers don't need to re-fetch + parse the TOML manifest. Schema matches the [payload.<type>] block below.
resources[].media object | null no Resolved from the optional [media] table: { "heroUrl": URL|null, "screenshotUrls": URL[], "videoUrl": URL|null }. All URLs absolute under payloadBaseUrl. null when the manifest declares no media — renderers fall back to type-specific auto-previews.

manifest.toml

Every resources/<id>/<version>/manifest.toml follows this shape. Shared fields at the top, then exactly one [payload.<type>] table.

Shared fields

id = "summarize-skill"                   # string, required, kebab-case
type = "skill"                           # enum, required
version = "0.1.0"                        # semver, required
name = "Summarize"                       # string, required
author = "Undra"                         # string, required
authorUrl = "https://www.undra.com"      # URL, optional
description = "Condense long docs..."    # string, required, ≤ 200 chars
longDescription = """
Markdown allowed here.
"""                                      # string, optional
license = "CC0-1.0"                      # SPDX, required
tags = ["ai", "writing"]                 # string[], optional
homepage = "https://..."                 # URL, optional
screenshots = ["screenshot.png"]         # string[], optional, relative to manifest
verified = true                          # bool, optional, default false
minAppVersion = "0.1.0"                  # semver, optional

Media (optional) — [media]

Presentation media for the storefront. Entirely optional — resources without media get an auto-generated preview (font specimen or a deterministic thumbnail). All paths are relative to the version dir and must exist on disk (validate-manifests.js enforces this).

[media]
hero = "hero.png"                        # string, optional — 16:9 recommended; card + detail hero
screenshots = ["shot-1.png", "shot-2.png"] # string[], optional — detail-page gallery
video = "demo.webm"                      # string, optional — detail-page muted looping autoplay
  • hero is shown as the card's cover image (object-fit cover, 16:9 region) and as the big visual atop the detail page. Aim for ≥ 1280×720.
  • screenshots render as a click-to-open grid on the detail page.
  • video should be short, silent, and small (webm/mp4) — it autoplays muted in a loop on the detail page.

Per-type payload

Exactly one of the following tables MUST be present, matching type.

Font — [payload.font]

[payload.font]
family = "Inter"
category = "sans-serif"                  # "sans-serif" | "serif" | "monospace" | "display"
variableFont = false

[[payload.font.faces]]
weight = 400
style = "normal"                         # "normal" | "italic"
file = "Inter-Regular.woff2"

[[payload.font.faces]]
weight = 700
style = "normal"
file = "Inter-Bold.woff2"

Installer behavior: downloads each faces[].file into ~/.undra/fonts/<id>/, registers the family with the renderer, exposes it in the font picker.

Skill — [payload.skill]

[payload.skill]
entry = "skill.md"                       # skill definition file (Anthropic-skills-style markdown frontmatter)
tools = ["workspace_search", "workspace_read"]
requiredCapabilities = ["workspace:read"]
optionalCapabilities = ["network:fetch"]

Installer behavior: downloads files into ~/.undra/skills/<id>/, registers with ai-runtime. Install dialog shows requiredCapabilities for user consent.

Agent — [payload.agent]

[payload.agent]
entry = "AGENT.md"                       # agent definition file (frontmatter + system prompt, Anthropic-skills-style)
model = "claude-opus-4-8"                # optional — pinned model id the agent runs on
icon = "icon.png"                        # optional — icon file shipped alongside the manifest
tools = ["undra_fetch_url", "undra_create_note"]
requiredCapabilities = ["workspace:read"]
optionalCapabilities = ["network:fetch"]

An agent is a downloadable AI assistant with a system prompt, a tool allowlist, and permissions, all declared in a single markdown file. The [payload.agent] table mirrors the skill table: entry names the markdown file that holds the agent definition (conventionally AGENT.md), whose frontmatter declares the name, description, tools, and permissions. Two agent-specific fields are allowed: model (an optional pinned model id the agent should run on) and icon (an optional icon file relative to the manifest). Both are optional; when model is absent the app picks its default model.

Installer behavior: downloads files into ~/.undra/agents/<id>/, registers with ai-runtime as a saved agent. Install dialog shows requiredCapabilities for user consent.

Extension — [payload.extension]

[payload.extension]
entry = "extension.js"                   # script-worker entry point
contributes = ["commands", "panels"]     # "commands" | "panels" | "item-types" | "views" | "ai-tools"
requiredCapabilities = ["workspace:read"]
optionalCapabilities = ["network:fetch", "ai:tools"]

Installer behavior: downloads files into ~/.undra/extensions/<id>/, loads into the existing script-worker sandbox, registers contributions with the extension host. Install dialog shows requiredCapabilities for user consent.


Capability vocabulary (v1)

Used by requiredCapabilities / optionalCapabilities on skills and extensions. The undra-app extension host gates each one.

Capability Grants
workspace:read Read items, folders, tags from the current workspace.
workspace:write Create, update, delete items.
network:fetch Make outbound HTTP requests.
ai:tools Call tools through the AI runtime.
ai:skills Invoke other registered skills.
storage:local Persist per-extension key/value data.
notifications Emit user-visible notifications.

This list will grow. Adding a capability is a coordinated change between the extension host (apps/desktop/src/extensions/runtime/capabilities.ts) and this document.


Versioning rules

  • New version of an existing resource = new sibling folder under resources/<id>/<new-version>/. Old version is never modified or moved.
  • Multiple versions appear as separate entries in index.json; the app picks the latest compatible (minAppVersion) by default.
  • Removing a version = removing the folder. The app will treat any locally installed copy as orphaned but functional.

Validation

scripts/validate-manifests.js (to be implemented) MUST verify:

  • Folder name <id>/<version> matches the manifest's id and version
  • All required shared fields present
  • Exactly one [payload.<type>] table present, matching type
  • All referenced files (files, faces[].file, entry, etc.) exist on disk
  • license is a known SPDX identifier
  • id is kebab-case, globally unique across the repo

← Browse the catalog · View raw markdown source