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": "welcome-note",
      "type": "template",
      "version": "0.1.0",
      "name": "Welcome to Undra",
      "author": "Undra",
      "description": "A first-run welcome note that introduces the workspace.",
      "license": "CC0-1.0",
      "tags": ["onboarding", "note"],
      "screenshots": [],
      "verified": true,
      "downloads": 0,
      "manifestUrl": "https://community.undra.com/resources/welcome-note/0.1.0/manifest.toml",
      "payloadBaseUrl": "https://community.undra.com/resources/welcome-note/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 "template" | "skill" | "extension" | "font" | "prompt"
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. { template: { files: [...] } }). Embedded so installers don't need to re-fetch + parse the TOML manifest. Schema matches the [payload.<type>] block below.

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 = "welcome-note"                      # string, required, kebab-case
type = "template"                        # enum, required
version = "0.1.0"                        # semver, required
name = "Welcome to Undra"                # string, required
author = "Undra"                         # string, required
authorUrl = "https://www.undra.com"      # URL, optional
description = "A first-run welcome..."   # string, required, ≤ 200 chars
longDescription = """
Markdown allowed here.
"""                                      # string, optional
license = "CC0-1.0"                      # SPDX, required
tags = ["onboarding", "note"]            # 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

Per-type payload

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

Template — [payload.template]

[payload.template]
itemType = "note"                        # "note" | "plan" | "canvas" | "dashboard"
files = ["welcome.md"]                   # files, relative to payloadBaseUrl
defaultTargetFolder = ""                 # "" = workspace root, else path

Installer behavior: copies files into <workspace>/.undra/templates/<id>/ and registers them with the item-create flow for itemType.

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.

Prompt — [payload.prompt]

[payload.prompt]
entry = "prompt.md"          # markdown file containing the prompt body
model = "claude-sonnet-4"    # optional — suggested model hint

Installer behavior: copies entry into <workspace>/.undra/prompts/<id>/<version>/. Prompts are deliberately tool-agnostic — they're just markdown text. Authors are encouraged to write prompts that work across Claude, Codex, Cursor, etc., not Undra-specific syntax.

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