# Package and manifest

A Grove extension is a directory of static assets with `manifest.json` at its root. A distributable `.grove` file is a zip archive of that directory. Mango accepts a manifest at the archive root or inside one wrapping directory.

## Manifest example

```json
{
  "grove": 1,
  "id": "com.example.grove.poll",
  "name": "Poll",
  "version": "1.0.0",
  "description": "Run quick polls in a channel.",
  "author": { "name": "Example Developer", "url": "https://example.com" },
  "icon": "icon.png",
  "readme": "README.md",
  "screenshots": [
    { "path": "screenshots/results.webp", "caption": "Poll results in a channel pane." }
  ],
  "entry": "index.html",
  "settings": "settings.html",
  "presentation": {
    "surfaces": ["channelPane", "window"],
    "defaultSurface": "channelPane",
    "minSize": { "width": 280, "height": 200 },
    "defaultSize": { "width": 340, "height": 420 }
  },
  "capabilities": ["channel.read", "channel.send", "storage"],
  "hostPermissions": ["https://api.example.com"]
}
```

## Top-level fields

| Field | Required | Contract |
|---|---:|---|
| `grove` | Yes | Integer API version. Mango 3.1 supports `1`. |
| `id` | Yes | Immutable, globally unique reverse-DNS identifier. It must contain a dot and cannot start or end with one. |
| `name` | Yes | Human-readable extension name shown by Mango. |
| `version` | Yes | Release version string. Semantic versions are required for catalogue update ordering. |
| `description` | No | Short store and consent description. |
| `author` | No | Object with required `name` and optional `url`. |
| `icon` | No | Package-relative image path. |
| `readme` | No | Package-relative `.md` or `.markdown` file used as the long store description. |
| `screenshots` | No | Up to eight package images with optional captions of at most 240 characters. |
| `entry` | Yes | Package-relative HTML entry document. |
| `settings` | No | Package-relative configuration page opened from Grove Manager. |
| `presentation` | No | Allowed surfaces, default surface, scope, and sizing hints. Omission defaults to a channel pane. |
| `contributions` | No | Static metadata for host integrations such as on-demand composer commands and upload providers. |
| `capabilities` | No | Permission names requested from the user. Unknown names make the manifest invalid. |
| `hostPermissions` | No | TLS network destinations available to the extension runtime. |

Optional arrays default to empty. Optional `presentation` defaults to a visible `channelPane` extension.

## Package resource validation

`entry`, `settings`, `icon`, `readme`, and screenshot paths must name files inside the package. Mango resolves symlinks and rejects:

- absolute or empty resource paths;
- `..` traversal or a symlink that escapes the package;
- missing resources or directories where a file is expected;
- a README without a Markdown extension;
- screenshots outside PNG, JPEG, or WebP;
- duplicate screenshot paths;
- more than eight screenshots or an overlong caption.

The package installer also bounds compressed size, expanded size, and entry count. Keep dependencies bundled, remove source maps or test fixtures that the runtime does not need, and do not fetch executable code from a remote host.

## Presentation

API 1 manifest surfaces are `channelPane`, `window`, `tab`, `composer`, and `headless`. `tab`, `composer`, and `headless` are exclusive contracts. A `tab` must be the only declared surface and must declare `"scope": "server"`; Mango rejects a mixed-surface or non-server tab manifest. The valid form creates one visible network-wide instance, and starting it again selects that existing tab. See [Surfaces and scopes](surfaces-and-scopes.md).

## Network destinations

`hostPermissions` is a network allow-list, separate from native capabilities. Declare the scheme and host:

```json
"hostPermissions": [
  "https://api.example.com",
  "wss://events.example.com"
]
```

An `https://` entry permits TLS web requests to that host; it does not grant WebSocket access. A `wss://` entry permits secure WebSockets and does not grant ordinary HTTPS requests. Cleartext `http://` and `ws://` entries do not become reachable.

Entries are origin-scoped and may include a port. Exact origins are preferred. A complete leftmost-label wildcard such as `https://*.example.com` matches one direct subdomain (`alice.example.com`), but not the apex (`example.com`), a deeper name (`www.alice.example.com`), another scheme, or an undeclared port. No other partial wildcard form is supported.

The broad forms `https://*` and `wss://*` are reserved for extensions whose destination is genuinely chosen by the user, such as a configurable upload provider. Broad access is displayed prominently during consent and should not be used when the service host is known in advance.

An empty allow-list means no external network access. The extension's own packaged assets, `data:`, `blob:`, and inert browser URLs remain available.

## Composer-command contributions

An exclusive composer utility may declare slash commands under `contributions.commands`. This makes the commands available while the utility is closed; Mango starts the utility only after the user invokes one and then delivers the ordinary `command` event:

```json
{
  "presentation": { "surfaces": ["composer"] },
  "contributions": {
    "commands": [
      {
        "name": "gif",
        "summary": "Find a GIF and add it to the draft",
        "usage": "[search|first] [keywords]",
        "subcommands": [
          { "name": "search", "summary": "Open the picker", "usage": "<keywords>" },
          { "name": "first", "summary": "Use the first result", "usage": "<keywords>" }
        ]
      }
    ]
  },
  "capabilities": ["commands.register"]
}
```

The manifest may declare at most 32 commands and 16 subcommands per command. Names are normalized to lowercase, cannot contain whitespace, must be unique inside the package, and cannot shadow a built-in Mango command. `summary`, `usage`, and `subcommands` are optional.

This form is only for the exclusive `composer` surface. A persistent visible Sprig or headless Root registers commands at runtime with `grove.commands.register(...)`. A composer utility must not call that registration method; its manifest remains the authoritative command list. Command invocation does not grant an IRC send route, and `grove.composer.complete(...)` still returns text to the draft without sending it.

## Upload-provider contributions

Only a headless Root may contribute an upload provider, and it must request `uploads.provide`:

```json
{
  "presentation": { "surfaces": ["headless"], "scope": "application" },
  "contributions": {
    "uploadProviders": [
      {
        "id": "primary",
        "name": "Private files",
        "kind": "userConfigured",
        "acceptsImagesOnly": false,
        "maxFileSize": 1073741824
      }
    ]
  },
  "capabilities": ["uploads.provide"]
}
```

A manifest may declare at most eight providers. A provider identifier is 1–64 lowercase letters, digits, `.`, `_`, or `-`; it must be unique inside the package. `kind` is `userConfigured` or `thirdParty`. `maxFileSize` is an optional positive byte count.

Application scope is recommended for one destination configured once and available on every network. Use server scope only when the extension deliberately keeps a separate uploader configuration for each IRC session.

See [Upload providers](upload-providers.md) for runtime registration.
