# iii-directory

> Engine introspection (functions / triggers / workers), workers registry proxy, and filesystem-backed skill + prompt reader.

| field | value |
|-------|-------|
| version | 0.8.1 |
| type | binary |
| repo | https://github.com/iii-hq/workers |
| supported_targets | x86_64-apple-darwin, aarch64-apple-darwin, i686-pc-windows-msvc, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, armv7-unknown-linux-gnueabihf |
| author | iii |

## installation

```sh
iii worker add iii-directory@0.8.1
```

## configuration

```yaml
- download_timeout_ms: 60000
  registry_url: https://api.workers.iii.dev
  skills_folder: ./skills
```

## readme

# iii-directory

Workers registry HTTP proxy and filesystem-backed skill + prompt
reader for the [iii engine](https://github.com/iii-hq/iii). Every
public function sits under a single `directory::*` namespace, split
into three sub-namespaces (all MCP-agnostic):

| Surface | What clients see | When to use it |
|---|---|---|
| **Skills** (`directory::skills::*`) | Enriched listing via `directory::skills::list` (`{ id, title, type, description, bytes, modified_at }` per row), a single-skill reader `directory::skills::get { id }` returning `{ id, title, type, description, body, modified_at }`, and `directory::skills::index` which renders a short per-worker overview document (one `## <title>` + first paragraph + `read more` link per `type: index` skill). `title` prefers the YAML frontmatter `title:` over the body H1; `type` is lifted from frontmatter `type:` (e.g. `index`, `how-to`, `reference`) and serialised as `null` when absent. | Orientation: "when and why to use my worker's tools" |
| **Prompts** (`directory::prompts::*`) | Static prompt templates listed by `directory::prompts::list` and read by `directory::prompts::get` | Parametric command templates the *user* invokes |
| **Registry** (`directory::registry::*`) | HTTP proxy over `api.workers.iii.dev` with `workers::{list,info}`. Rows share the core `name` / `description` / `version` fields with the engine's `engine::workers::list` and add publication metadata (`type`, `config`, `supported_targets`, `total_downloads`, `dependencies`, optional `image`). `workers::list` is cursor-paginated with a server-authored page size. | "What's published in the public registry?" |

Engine introspection (functions / triggers / registered triggers /
workers) is served by the engine natively at
`engine::functions::*`, `engine::triggers::*`,
`engine::registered-triggers::*`, and `engine::workers::*`. Earlier
versions of this crate wrapped those calls under `directory::engine::*`
helpers; the wrappers have been removed — call the engine ids
directly.

Skills and prompts are sourced from a single configured folder on disk
(`skills_folder`). The only write path is the
**`directory::skills::download`** function, which pulls markdown into
`skills_folder` from either the
[workers registry](https://workers.iii.dev) or a GitHub repo. Once
downloaded, files belong to the developer — edit them however you want.

`directory::registry::workers::*` and the engine's `engine::workers::*`
share the core `name` / `description` / `version` fields so a parser
that touches only those keys works against either surface; the
registry view also surfaces publication metadata (`type`, `config`,
`supported_targets`, `total_downloads`, `dependencies`, optional
`image`) and the engine view adds runtime / connection state.

## Table of contents

1. [Install](#install)
2. [Configuration](#configuration)
3. [Quickstart: download some skills](#quickstart-download-some-skills)
4. [On-disk layout](#on-disk-layout)
5. [Skill ids](#skill-ids)
6. [Functions](#functions)
7. [Custom trigger types](#custom-trigger-types)
8. [Local development & testing](#local-development--testing)
9. [Migration from skills v0.2.x](#migration-from-skills-v02x)

---

## Install

```bash
iii worker add iii-directory
```

`iii worker add` fetches the binary, writes a config block into
`~/.iii/config.yaml`, and the engine starts the worker on the next
`iii start`.

---

## Configuration

```yaml
# Folder that backs every read (`directory::skills::list`,
# `directory::skills::get`, `directory::prompts::*`) and every write
# from `directory::skills::download`. Relative paths are resolved
# against the process current working directory; absolute paths are
# used as-is.
skills_folder: ./skills

# Workers registry base URL — used by `directory::skills::download`
# and the `directory::registry::*` proxies when a `worker=` source is
# specified. Override for self-hosted deployments.
registry_url: https://api.workers.iii.dev

# Timeout for a single download (`git clone` or HTTP request) in ms.
download_timeout_ms: 60000
```

The folder is created on first download if it doesn't exist.

---

## Quickstart: download some skills

```bash
# Pull a specific worker's skills + prompts at a fixed semver from
# the registry. Files land under `<skills_folder>/agent-memory/`.
iii trigger --function-id=directory::skills::download \
  --payload='{"worker": "agent-memory", "version": "1.2.3"}'

# Same, but always fetch whatever's tagged `latest` (also the default
# when neither version nor tag is given).
iii trigger --function-id=directory::skills::download \
  --payload='{"worker": "agent-memory"}'

# Pull a single subfolder out of a public GitHub repo via
# `git clone --depth 1 --branch main`. Files land under
# `<skills_folder>/frontend-design/`. The `branch` field defaults to
# `main`; pass `"master"` for older repos that haven't migrated.
iii trigger --function-id=directory::skills::download \
  --payload='{
    "repo": "https://github.com/anthropics/skills",
    "skill": "frontend-design"
  }'
```

The response is `{ namespace, skills_written, prompts_written, source }`
where `skills_written` and `prompts_written` are arrays of relative
paths / prompt names that were materialised in this run.

After every successful download the worker fires the
`directory::skills::on-change` and/or `directory::prompts::on-change`
trigger types so that subscribers like the [`mcp`](../mcp/) worker can
forward MCP `notifications/list_changed` to their clients.

---

## On-disk layout

The worker assumes a fixed layout under `skills_folder`:

```text
skills_folder/
  <namespace>/                 # one folder per `directory::skills::download` namespace
    index.md                   # → iii://<namespace>/index
    contacts.md                # → iii://<namespace>/contacts
    emails/send-email.md       # → iii://<namespace>/emails/send-email
    prompts/                   # ← magic marker for prompts
      send-email.md            # ← MCP slash-command (needs YAML frontmatter)
      triage.md
```

A few rules:

- **Skill ids** are the relative path under `skills_folder` with `.md`
  stripped. Each segment must satisfy `[a-z0-9_-]{1,64}`.
- **Skill frontmatter is optional.** When present, the reader honours
  two keys: `title:` (used by `directory::skills::list` and
  `directory::skills::get` in preference to a body `# H1`) and
  `type:` (free-form classifier surfaced verbatim on both responses).
  Any other YAML keys are ignored.
- **Prompts** live under any `*/prompts/*.md` path. They must start with
  a YAML frontmatter block declaring at least `description`; `name`
  is optional and overrides the file-stem default.
- Files anywhere else (i.e. *not* in a `prompts/` segment) are skills.

The download function namespaces by source:

| Source | Destination |
|---|---|
| `repo=URL skill=NAME branch?=main` | `<skills_folder>/<NAME>/...` |
| `worker=NAME version=…` | `<skills_folder>/<NAME>/...` |
| `worker=NAME tag=…` (default `tag=latest`) | `<skills_folder>/<NAME>/...` |

Re-pulling the same source overwrites files **file-by-file** —
existing siblings outside the response set are preserved (so
hand-edited additions survive a re-pull).

---

## Skill ids

Skills are addressed by their relative path under `skills_folder` with
`.md` stripped — e.g. `<skills_folder>/agent-memory/observe.md` →
id `"agent-memory/observe"`. The same string is what
`directory::skills::list` returns and what `directory::skills::get`
expects in `{ "id": ... }`. The legacy `iii://{id}` link form is still
accepted on `get` (the prefix is auto-stripped), but the worker no
longer parses any other `iii://` URI shape — bodies are read solely by
id, and the auto-rendered tree-shaped index that previous releases
served at `iii://directory/skills` is gone. Consumers that want a
tree-shaped picker iterate `list` rows themselves and indent by
`id.matches('/').count()`.

---

## Functions

Sixteen functions, all under `directory::*`. All registrations are
namespace-clean; this worker is intentionally agnostic to MCP and any
other adapter.

### `directory::skills::*` (filesystem reader)

| Function ID | Description |
|---|---|
| `directory::skills::download` | Pull markdown into `skills_folder`. Either `{repo, skill, branch?}` (defaults `branch=main`) or `{worker, version?|tag?}` (defaults `tag=latest`). |
| `directory::skills::list` | Enriched listing of every fs-backed skill: `{ id, title, type, description, bytes, modified_at }` per row. `title` prefers the YAML frontmatter `title:` over the body H1, `type` is lifted from frontmatter `type:` (`null` when absent), and `description` is the first paragraph of the body — so consumers can render a picker without a follow-up `get` per row. |
| `directory::skills::get` | Fetch one skill by id. Returns `{ id, title, type, description, body, modified_at }` — same shape `directory::skills::list` rows use, plus the raw markdown `body`. Same title-resolution and `type` precedence as `list`. Accepts a bare id or the same id prefixed with `iii://`. |
| `directory::skills::index` | Render one short markdown entry per installed worker (skills with frontmatter `type: index`). Returns `{ body, workers_count }` where `body` is a ready-to-paste page: `# Skills index`, then one `## <worker title>` heading + the worker's first overview paragraph + a `Read iii://<ns>/index` pointer the agent can follow with `directory::skills::get`. Token-light by design; use `directory::skills::list` for per-skill rows. |

### `directory::prompts::*` (filesystem reader)

| Function ID | Description |
|---|---|
| `directory::prompts::list` | Metadata-only listing of every fs-backed prompt. |
| `directory::prompts::get` | Fetch one prompt's body + `{name, description, modified_at}`. Plain shape, no envelope. |

### Engine introspection (native)

Engine introspection is no longer wrapped here. Call the engine's
native ids directly — every one takes the same filters
(`prefix`, `search`, `worker`, `include_internal` where applicable):

| Function ID | Description |
|---|---|
| `engine::functions::list` | List functions registered with the engine. |
| `engine::functions::info` | Single-function detail: schemas, owning worker. |
| `engine::triggers::list` | List trigger TYPES (the providers, e.g. `http`, `cron`). |
| `engine::triggers::info` | Single trigger-type detail: configuration schema, return schema. |
| `engine::registered-triggers::list` | List trigger INSTANCES (subscriber rows). |
| `engine::registered-triggers::info` | Single registered-trigger detail. |
| `engine::workers::list` | List workers with an open engine WS connection. Daemon-managed providers (`iii-http`, `iii-cron`, `iii-state`) won't appear — call `worker::list` from the supervisor to see those. |
| `engine::workers::info` | One worker's detail by `name`. |

### `directory::registry::*` (workers registry HTTP proxy)

| Function ID | Description |
|---|---|
| `directory::registry::workers::list` | Browse / search published workers in `api.workers.iii.dev`. Optional free-text `search` (matched fuzzy by `pg_trgm`) and opaque `cursor` for pagination; page size is server-authored. Response is `{ workers: [...], pagination: { next_cursor, has_more, page_size } }`. Shares the core `name` / `description` / `version` fields with the engine's `engine::workers::list`. |
| `directory::registry::workers::info` | Full registry detail for one worker. Fans out two parallel registry calls — `GET /w/{slug}` for the worker envelope (publication metadata + readme + functions + triggers) and `GET /w/{slug}/skills` for the skills/prompts tree — and merges them into `{ worker, readme, api_reference, skills_tree }`. The user-facing input still accepts `version:` (semver) or `tag:` (e.g. `latest`); both go on the wire as `?version=…`. |

Both `directory::registry::*` responses are cached in-process for
`registry_cache_ttl_ms` (default 60s).

There is **no** `directory::skills::register` /
`directory::prompts::register` — see
[Migration](#migration-from-skills-v02x) below.

---

## Custom trigger types

| Trigger type | Fires when | Payload to subscribers |
|---|---|---|
| `directory::skills::on-change` | After a `directory::skills::download` that wrote at least one skill markdown file | `{ "op": "download", "namespace": "<ns>", "source": "repo" \| "registry" }` |
| `directory::prompts::on-change` | After a `directory::skills::download` that wrote at least one prompt markdown file | `{ "op": "download", "namespace": "<ns>", "source": "repo" \| "registry" }` |

Dispatches are fire-and-forget (Void), so the download path doesn't
block on downstream latency.

---

## Local development & testing

### Run from source

```bash
cargo run --release -- --url ws://127.0.0.1:49134 --config ./config.yaml
```

### Tests

```bash
# Fast, offline — exercises the pure helpers (markdown / id validators
# / fs source) without needing an iii engine.
cargo test --lib

# Full BDD suite — requires an iii engine on ws://127.0.0.1:49134
# (or III_ENGINE_WS_URL). The git-backed download scenarios spin up
# a local fixture repo via `git init`; the registry-backed scenarios
# point a wiremock server at the worker's `registry_url` config.
cargo test

# One feature group at a time. Available tags:
#   @engine  @read  @prompts  @download  @download_repo  @download_registry
cargo test --test bdd -- --tags @download
```

The BDD harness lives under [tests/](tests/). Feature files mirror the
modules in [src/functions/](src/functions/). Step definitions under
[tests/steps/](tests/steps/) drive each feature through the same
`iii.trigger` path the production binary uses.

## api reference

```json
{
  "functions": [
    {
      "description": "Fetch one filesystem-backed prompt by name. Returns the raw markdown body plus name, description, and modified_at — no envelope, no templating.",
      "metadata": {},
      "name": "directory::prompts::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "PromptGetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "body": {
            "description": "Raw markdown body (post-frontmatter) from disk.",
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "modified_at": {
            "description": "File mtime as RFC 3339.",
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "body",
          "description",
          "modified_at",
          "name"
        ],
        "title": "PromptGetOutput",
        "type": "object"
      }
    },
    {
      "description": "List filesystem-backed prompts (name, description, modified_at) from skills_folder.",
      "metadata": {},
      "name": "directory::prompts::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ListPromptsInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "PromptEntry": {
            "properties": {
              "description": {
                "type": "string"
              },
              "modified_at": {
                "description": "File mtime as RFC 3339.",
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "description",
              "modified_at",
              "name"
            ],
            "type": "object"
          }
        },
        "properties": {
          "prompts": {
            "items": {
              "$ref": "#/definitions/PromptEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "prompts"
        ],
        "title": "ListPromptsOutput",
        "type": "object"
      }
    },
    {
      "description": "Download one skill folder from a GitHub repo into skills_folder. `repo` (the repo URL) and `skill` (the subfolder under `skills/`, which also names the destination namespace) are required; `branch` defaults to \"main\". The repo URL is validated (https / ssh / git@ only). To pull a published worker instead, use directory::skills::download_from_registry.",
      "metadata": {
        "tool": {
          "label": "Download skills (repo)"
        }
      },
      "name": "directory::skills::download_from_repo",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input for `directory::skills::download_from_repo`. The required `repo` + `skill` fields make this function's source unambiguous at the schema level.",
        "properties": {
          "branch": {
            "default": null,
            "description": "Branch to clone. Defaults to `\"main\"`.",
            "type": [
              "string",
              "null"
            ]
          },
          "repo": {
            "description": "GitHub repo URL (validated: https / ssh / git@ only).",
            "type": "string"
          },
          "skill": {
            "description": "Subfolder under `skills/` inside the repo. Doubles as the destination namespace inside `skills_folder`.",
            "type": "string"
          }
        },
        "required": [
          "repo",
          "skill"
        ],
        "title": "RepoDownloadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "namespace": {
            "type": "string"
          },
          "prompts_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "skills_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "source": true
        },
        "required": [
          "namespace",
          "prompts_written",
          "skills_written",
          "source"
        ],
        "title": "DownloadOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: auto-download skills on worker add event.",
      "metadata": {},
      "name": "directory::__on_worker_added",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "AnyValue"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "AnyValue"
      }
    },
    {
      "description": "Download skills + prompts into skills_folder from EITHER source. Prefer the explicit directory::skills::download_from_registry / directory::skills::download_from_repo, whose schemas can't be mixed up. Pass {repo, skill, branch?} to clone one skill folder from a GitHub repo (branch defaults to \"main\"), or {worker, version?|tag?} to pull from the workers registry (tag defaults to \"latest\"). Specify exactly ONE source set. Files in the destination namespace are overwritten file-by-file.",
      "metadata": {
        "tool": {
          "label": "Download skills"
        }
      },
      "name": "directory::skills::download",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "branch": {
            "default": null,
            "description": "Source A: branch to clone. Defaults to `\"main\"`. Pass `\"master\"` (or any other branch name) for repos whose default branch is not `main`.",
            "type": [
              "string",
              "null"
            ]
          },
          "repo": {
            "default": null,
            "description": "Source A: GitHub repo URL. Pair with `skill`.",
            "type": [
              "string",
              "null"
            ]
          },
          "skill": {
            "default": null,
            "description": "Source A: subfolder under `skills/` inside the repo. Doubles as the destination namespace inside `skills_folder`.",
            "type": [
              "string",
              "null"
            ]
          },
          "tag": {
            "default": null,
            "description": "Source B: registry tag to pull (e.g. `latest`). Mutually exclusive with `version`. Defaults to `\"latest\"` when neither `version` nor `tag` is provided.",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "default": null,
            "description": "Source B: explicit semver to pull. Mutually exclusive with `tag`.",
            "type": [
              "string",
              "null"
            ]
          },
          "worker": {
            "default": null,
            "description": "Source B: workers registry name. Pair with exactly one of `version` / `tag`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "DownloadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "namespace": {
            "type": "string"
          },
          "prompts_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "skills_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "source": true
        },
        "required": [
          "namespace",
          "prompts_written",
          "skills_written",
          "source"
        ],
        "title": "DownloadOutput",
        "type": "object"
      }
    },
    {
      "description": "Download one worker's skills + prompts from the workers registry into skills_folder. `worker` is required; pass either `version` (exact semver) OR `tag` (e.g. \"latest\", the default when both are omitted), not both. Files in the destination namespace are overwritten file-by-file. A missing worker returns a `D310 not_found` naming the next function to call. To pull from a GitHub repo instead, use directory::skills::download_from_repo.",
      "metadata": {
        "tool": {
          "label": "Download skills (registry)"
        }
      },
      "name": "directory::skills::download_from_registry",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input for `directory::skills::download_from_registry`. The required `worker` field is what makes this function's source unambiguous at the schema level.",
        "properties": {
          "tag": {
            "default": null,
            "description": "Registry tag to pull (e.g. `\"latest\"`). Mutually exclusive with `version`. Defaults to `\"latest\"` when neither is provided.",
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "default": null,
            "description": "Explicit semver to pull. Mutually exclusive with `tag`.",
            "type": [
              "string",
              "null"
            ]
          },
          "worker": {
            "description": "Worker name in the registry (e.g. `\"shell\"`).",
            "type": "string"
          }
        },
        "required": [
          "worker"
        ],
        "title": "RegistryDownloadInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "namespace": {
            "type": "string"
          },
          "prompts_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "skills_written": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "source": true
        },
        "required": [
          "namespace",
          "prompts_written",
          "skills_written",
          "source"
        ],
        "title": "DownloadOutput",
        "type": "object"
      }
    },
    {
      "description": "List workers from the public registry (api.workers.iii.dev). Optional free-text `search` is matched fuzzily by the registry; omit it to browse by `total_downloads DESC`. Pagination is cursor-based with a server-authored page size — pass back `pagination.next_cursor` as `cursor` to fetch the next page. Shares the core `name` / `description` / `version` fields with the engine's `engine::workers::list`. Results are cached for `registry_cache_ttl_ms` (default 60s).",
      "metadata": {},
      "name": "directory::registry::workers::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`directory::registry::workers::list` input. Mirrors the engine's `engine::workers::list` search input so callers can switch between local and registry surfaces without re-learning the API. Adds `cursor` for paging because the registry is paged (server-authored page size — the client cannot override it).",
        "properties": {
          "cursor": {
            "default": null,
            "description": "Opaque cursor returned by a previous call's `pagination.next_cursor`. Pass back verbatim to fetch the next page; omit (or pass `null`) to fetch the first page.",
            "type": [
              "string",
              "null"
            ]
          },
          "search": {
            "default": null,
            "description": "Optional free-text query. Forwarded to the registry as `?search=…`; the registry ranks results by `pg_trgm` similarity against `lower(name)` and `lower(description)`. When omitted, results are ordered by `total_downloads DESC`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "WorkerListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Dependency": {
            "description": "Worker dependency entry. Mirrors the `Dependency` schema in `openapi.yaml`.",
            "properties": {
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "version"
            ],
            "type": "object"
          },
          "Pagination": {
            "description": "Pagination envelope returned alongside a worker-list page. Mirrors the OpenAPI `Pagination` schema.",
            "properties": {
              "has_more": {
                "default": false,
                "type": "boolean"
              },
              "next_cursor": {
                "default": null,
                "description": "Opaque cursor for the next page. `null` on the last page.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "page_size": {
                "default": 0,
                "description": "Server-authored page size. The client cannot override this.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "Worker": {
            "description": "Shared worker envelope used by both `directory::registry::workers::list` rows and the `worker` field of `directory::registry::workers::info`. Field names match the OpenAPI `WorkerListItem` schema. The shared core fields (`name`, `description`, `version`) line up with the engine's `engine::workers::list` row shape so callers learn one envelope across local + registry surfaces.",
            "properties": {
              "author": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WorkerAuthor"
                  },
                  {
                    "type": "null"
                  }
                ],
                "default": null
              },
              "config": {
                "default": {},
                "description": "Free-form runtime configuration block from the publish payload."
              },
              "dependencies": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Dependency"
                },
                "type": "array"
              },
              "description": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "image": {
                "description": "Container image tag, populated only for `type=image` workers.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              },
              "repo": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "supported_targets": {
                "default": [],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "total_downloads": {
                "default": 0,
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "type": {
                "default": null,
                "description": "Worker kind — `binary`, `image`, or `engine`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "version": {
                "default": null,
                "description": "Latest published version (worker-list) or the resolved version (worker-info, when called with `version` / `tag`).",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "WorkerAuthor": {
            "description": "Author block for a published worker. Field names match the `WorkerAuthor` schema in `openapi.yaml` (`pfp`, `verified`).",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "pfp": {
                "default": null,
                "description": "Profile picture URL. `null` when the author hasn't uploaded one.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "verified": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "pagination": {
            "$ref": "#/definitions/Pagination"
          },
          "workers": {
            "items": {
              "$ref": "#/definitions/Worker"
            },
            "type": "array"
          }
        },
        "required": [
          "pagination",
          "workers"
        ],
        "title": "WorkerListOutput",
        "type": "object"
      }
    },
    {
      "description": "Fetch full registry metadata for one worker: worker envelope (same core fields as the engine's `engine::workers::list` row shape, plus registry-only `type` / `config` / `supported_targets` / `total_downloads` / `dependencies` / `image`), readme, full API reference (functions + triggers schemas), and the tree of skill / prompt file paths fetched from the registry's /w/{slug}/skills endpoint. Pass either `version` or `tag` (defaults to tag=\"latest\"). Results are cached for `registry_cache_ttl_ms`.",
      "metadata": {},
      "name": "directory::registry::workers::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`directory::registry::workers::info` input. Pass either `version` or `tag`; if neither is provided we fall back to `tag: \"latest\"`.",
        "properties": {
          "name": {
            "description": "Worker name in the registry (e.g. `\"resend\"`).",
            "type": "string"
          },
          "tag": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "default": null,
            "description": "Mutually exclusive with `tag`. If neither is provided we fall back to `tag: \"latest\"`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name"
        ],
        "title": "WorkerInfoInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ApiReference": {
            "properties": {
              "functions": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/ApiReferenceFunction"
                },
                "type": "array"
              },
              "triggers": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/ApiReferenceTrigger"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "ApiReferenceFunction": {
            "properties": {
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "metadata": true,
              "name": {
                "type": "string"
              },
              "request_schema": true,
              "response_schema": true
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "ApiReferenceTrigger": {
            "properties": {
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "invocation_schema": true,
              "metadata": true,
              "name": {
                "type": "string"
              },
              "return_schema": true
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "Dependency": {
            "description": "Worker dependency entry. Mirrors the `Dependency` schema in `openapi.yaml`.",
            "properties": {
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "version"
            ],
            "type": "object"
          },
          "SkillsTree": {
            "properties": {
              "prompts": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/SkillsTreePrompt"
                },
                "type": "array"
              },
              "skills": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/SkillsTreeSkill"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "SkillsTreePrompt": {
            "properties": {
              "description": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "SkillsTreeSkill": {
            "properties": {
              "path": {
                "type": "string"
              }
            },
            "required": [
              "path"
            ],
            "type": "object"
          },
          "Worker": {
            "description": "Shared worker envelope used by both `directory::registry::workers::list` rows and the `worker` field of `directory::registry::workers::info`. Field names match the OpenAPI `WorkerListItem` schema. The shared core fields (`name`, `description`, `version`) line up with the engine's `engine::workers::list` row shape so callers learn one envelope across local + registry surfaces.",
            "properties": {
              "author": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WorkerAuthor"
                  },
                  {
                    "type": "null"
                  }
                ],
                "default": null
              },
              "config": {
                "default": {},
                "description": "Free-form runtime configuration block from the publish payload."
              },
              "dependencies": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/Dependency"
                },
                "type": "array"
              },
              "description": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "image": {
                "description": "Container image tag, populated only for `type=image` workers.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              },
              "repo": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "supported_targets": {
                "default": [],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "total_downloads": {
                "default": 0,
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "type": {
                "default": null,
                "description": "Worker kind — `binary`, `image`, or `engine`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "version": {
                "default": null,
                "description": "Latest published version (worker-list) or the resolved version (worker-info, when called with `version` / `tag`).",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name"
            ],
            "type": "object"
          },
          "WorkerAuthor": {
            "description": "Author block for a published worker. Field names match the `WorkerAuthor` schema in `openapi.yaml` (`pfp`, `verified`).",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "pfp": {
                "default": null,
                "description": "Profile picture URL. `null` when the author hasn't uploaded one.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "verified": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "api_reference": {
            "$ref": "#/definitions/ApiReference"
          },
          "readme": {
            "type": [
              "string",
              "null"
            ]
          },
          "skills_tree": {
            "$ref": "#/definitions/SkillsTree"
          },
          "worker": {
            "allOf": [
              {
                "$ref": "#/definitions/Worker"
              }
            ],
            "description": "Same shape as `directory::registry::workers::list` rows (and the engine's `engine::workers::list` rows for the shared core fields)."
          }
        },
        "required": [
          "api_reference",
          "skills_tree",
          "worker"
        ],
        "title": "WorkerInfoOutput",
        "type": "object"
      }
    },
    {
      "description": "Render a per-WORKER overview: one short markdown block per installed worker (each worker's root overview doc `<ns>/index`, whether or not it declares frontmatter `type: index`). Each block is a `## <worker title>` heading, the first paragraph of that worker's overview, and a `directory::skills::get` call to read the full reference. Token-light by design and intended for system-prompt injection; for individual per-SKILL rows call directory::skills::list.",
      "metadata": {},
      "name": "directory::skills::index",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "IndexSkillsInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "body": {
            "description": "Rendered markdown document — one short `## <title>` block per installed worker (each worker's root overview doc, whether or not it declares frontmatter `type: index`), carrying the worker's first-paragraph overview and a `directory::skills::get` call to read the full reference. Sorted lex by id.",
            "type": "string"
          },
          "workers_count": {
            "description": "Number of worker entries rendered (i.e. the count of worker overview rows that survived the filter). Cheap sanity check that doesn't require re-parsing the body.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "body",
          "workers_count"
        ],
        "title": "IndexSkillsOutput",
        "type": "object"
      }
    },
    {
      "description": "Full detail for one engine function: schemas, owning worker, and registered triggers that target it. Proxies to the engine's native engine::functions::info for the core data.",
      "metadata": {},
      "name": "directory::engine::functions::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "function_id": {
            "description": "Fully-qualified function id on the bus (e.g. `sandbox::create`).",
            "type": "string"
          }
        },
        "required": [
          "function_id"
        ],
        "title": "FunctionInfoInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "RegisteredTriggerSummary": {
            "description": "Trigger instance summary for the response envelope.",
            "properties": {
              "config": true,
              "id": {
                "type": "string"
              },
              "trigger_type": {
                "type": "string"
              }
            },
            "required": [
              "config",
              "id",
              "trigger_type"
            ],
            "type": "object"
          }
        },
        "description": "Response shape for `directory::engine::functions::info`.\n\nMirrors the shape of the old `directory::engine::functions::info` but WITHOUT the `how_guide` and `related_skills` fields.",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "function_id": {
            "type": "string"
          },
          "metadata": true,
          "registered_triggers": {
            "items": {
              "$ref": "#/definitions/RegisteredTriggerSummary"
            },
            "type": "array"
          },
          "request_schema": true,
          "response_schema": true,
          "worker_name": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "function_id",
          "registered_triggers"
        ],
        "title": "FunctionInfoOutput",
        "type": "object"
      }
    },
    {
      "description": "Fetch one filesystem-backed skill by id and return its raw markdown body plus id, title, type, function_id, and modified_at. A worker overview is addressed by the bare worker name (e.g. \"iii-sandbox\") — that is the id `list`/`index` hand back. Input is forgiving: \"iii-sandbox/index\", \"iii-sandbox/SKILL.md\", a trailing \".md\", and an iii:// prefix all resolve to the same overview; and if the exact id misses, the worker name is matched case-insensitively as a substring (\"sandbox\" finds \"iii-sandbox\"). `title` prefers frontmatter `title:` over the body H1; `type` is the frontmatter `type:`. There is no `description` field here (the body already opens with that paragraph) — use directory::skills::list for the teaser-only view. On a miss you get a `D110 not_found` message naming the closest ids and the next function to call.",
      "metadata": {
        "tool": {
          "label": "Get skill"
        }
      },
      "name": "directory::skills::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "id": {
            "description": "Skill id (the same string returned by `directory::skills::list`, e.g. `\"directory/skills/list\"`). Two ergonomic variants are also accepted: the file-path form `<id>.md` (the trailing `.md` is stripped) and the legacy `iii://{id}` URI form. Other URI schemes are rejected. The filename `SKILLS.md` is aliased to `index.md` to match the filesystem scanner.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "title": "SkillGetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "body": {
            "description": "Raw markdown body (post-frontmatter) from disk.\n\nNote: there is no `description` field. `description` is the body's first paragraph, which is already inside `body` — every caller asking for the body would otherwise pay for the prefix twice. Use `directory::skills::list` rows when you want the teaser without the full body.",
            "type": "string"
          },
          "function_id": {
            "description": "Frontmatter `function_id:` when present — the canonical bus function id this skill documents (e.g. `sandbox::create`). The response's `id` field is the SKILL path on disk; `function_id` is what the agent should pass to `agent_trigger`. `null` when the skill isn't 1:1 with a single function.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "modified_at": {
            "description": "File mtime as RFC 3339.",
            "type": "string"
          },
          "title": {
            "description": "Frontmatter `title:` when present and non-empty, otherwise the first `# H1` line in the body, otherwise the bare `id`.",
            "type": "string"
          },
          "type": {
            "description": "Frontmatter `type:` (e.g. `index`, `how-to`, `reference`). `null` when the file has no frontmatter or omits the key.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "body",
          "id",
          "modified_at",
          "title"
        ],
        "title": "SkillGetOutput",
        "type": "object"
      }
    },
    {
      "description": "List skills as one row PER SKILL (id, title, type, function_id, description, bytes, modified_at) from skills_folder — use this when you need individual skill ids. A worker overview row's `id` is the bare worker name (e.g. `iii-sandbox`); pass it straight to directory::skills::get. For a per-WORKER overview instead, call directory::skills::index. Filters: `search` (case-insens. substring vs id+title+description), `prefix` (worker-namespace prefix; matches the overview row and its sub-skills), `type` (exact frontmatter type match). Pass `include_description: false` for token-light id+title+type rows (default: descriptions included). `title` prefers frontmatter `title:` over the body H1. Each row's `function_id` is the callable bus id (e.g. `sandbox::create`) — pass THAT to agent_trigger, not the row's `id` (which is a documentation address).",
      "metadata": {},
      "name": "directory::skills::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "include_description": {
            "default": null,
            "description": "When `false`, the response omits the first-paragraph `description` field on every row. Useful for token-light pickers that only need `id` + `title` + `type`. Default `true`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "prefix": {
            "default": null,
            "description": "Exact prefix match against `id`. Combine with `search` to scope a fuzzy match to one worker namespace, e.g. `prefix: \"sandbox/\"`.",
            "type": [
              "string",
              "null"
            ]
          },
          "search": {
            "default": null,
            "description": "Case-insensitive substring match against `id`, `title`, and (when `include_description` is true) the first body paragraph. Omitted rows are filtered out cheaply on the FsSkill { id } pass before the per-file frontmatter read, so a narrowed list is dramatically cheaper for the caller than the unfiltered one.",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "default": null,
            "description": "Exact match against the frontmatter `type:` field (`index`, `how-to`, `reference`, ...). `null` for entries with no frontmatter `type:`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "ListSkillsInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SkillEntry": {
            "properties": {
              "bytes": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "description": {
                "description": "First paragraph of the body, empty when the file has only headings. Also empty when the caller passed `list { include_description: false }` for a token-light row.",
                "type": "string"
              },
              "function_id": {
                "description": "Frontmatter `function_id:` when present — the canonical bus function id this skill documents (e.g. `sandbox::create`). The row's `id` field is the SKILL path on disk (e.g. `sandbox/skills/sandbox/create`); `function_id` is what an agent should pass to `agent_trigger`. `null` for skills that aren't 1:1 with a single function (index/reference).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "modified_at": {
                "description": "File mtime as RFC 3339 (best effort; empty if unavailable).",
                "type": "string"
              },
              "title": {
                "description": "Frontmatter `title:` when present and non-empty, otherwise the first `# H1` line in the body, otherwise the bare `id`.",
                "type": "string"
              },
              "type": {
                "description": "Frontmatter `type:` (e.g. `index`, `how-to`, `reference`). `null` when the file has no frontmatter or omits the key.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "bytes",
              "description",
              "id",
              "modified_at",
              "title"
            ],
            "type": "object"
          }
        },
        "properties": {
          "skills": {
            "items": {
              "$ref": "#/definitions/SkillEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "skills"
        ],
        "title": "ListSkillsOutput",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Fires after every successful directory::skills::download that wrote at least one prompt markdown file.",
      "invocation_schema": {},
      "metadata": {},
      "name": "directory::prompts::on-change",
      "return_schema": {}
    },
    {
      "description": "Fires after every successful directory::skills::download that wrote at least one skill markdown file.",
      "invocation_schema": {},
      "metadata": {},
      "name": "directory::skills::on-change",
      "return_schema": {}
    }
  ]
}
```
