Extensions

Packaging and installing

The extension package format, manifest fields, permissions, and how packages are installed and updated.

Package layout#

A package is a directory:

output
acme.hello/
  extension.json        # manifest
  component.wasm        # the component (name configurable via "component")
  skills/ prompts/ themes/ docs/   # optional, declared under "resources"
  bin/                  # optional native binaries (declared under "binaries")

Minimal manifest:

json
{
  "apiVersion": "oino.extension/2",
  "id": "acme.hello",
  "name": "Hello",
  "version": "0.1.0",
  "kind": "extension",
  "description": "Says hello.",
  "permissions": { "network": { "hosts": ["api.example.com"] } }
}

apiVersion must be oino.extension/2; packages written for the 1.x ABI are rejected with a clear error. kind is extension (default) or provider (must list provides.apis and export the provider interface).

Manifest fields#

Field Meaning
id, name, version, description, license, authors identity; id names the state dir and excludes packages
component component file name (default component.wasm)
checksums file → "sha256:<hex>", verified on install
minOinoVersion minimum host version
provides for providers: apis (wire APIs) and providers (provider ids)
resources skills, prompts, themes (directories), docs (directory)
binaries native CLI binaries shipped in bin/
limits memoryMiB (default 512, max 2048), hookTimeoutMs (5000), toolCallHookTimeoutMs
ui reserved entry ids and static settings namespaces
permissions capability grants, below

Permissions#

Everything is denied unless listed. The permissions object:

json
{
  "permissions": {
    "sessionControl": true,
    "sessions": { "readAll": true },
    "tools": { "override": ["bash"] },
    "models": { "complete": true },
    "credentials": ["anthropic"],
    "agents": { "spawn": true },
    "process": { "spawn": true, "allow": ["git", "cargo"] },
    "network": { "hosts": ["api.example.com"], "listenLoopback": false },
    "fs": { "project": "read", "agentDir": "read" },
    "env": ["HOME"],
    "secrets": true,
    "ui": true,
    "osNotify": true,
    "openUrl": true,
    "clipboard": { "read": true, "write": true },
    "editExternally": true,
    "services": { "call": ["acme.other"] },
    "hostServices": ["browser"]
  }
}

ui is the only permission that defaults to granted. Calling a capability you did not list fails at the import with permission denied: <permission>; the failure is a normal error you can handle, and the host logs it.

Installing#

Three routes, from casual to permanent:

bash
oino -e ./acme.hello                     # one run
cp -r acme.hello ~/.oino/extensions/     # global install
oino install ./acme.hello                # package manager (records the source)

oino install accepts a directory, an archive, an https archive URL or a git+https URL, with --project (-l) to install into the project scope and --yes (-y) to skip prompts. Related commands:

bash
oino list                 # distribution, global, project packages
oino remove <id> [-l]     # uninstall (distribution packages are refused)
oino update <id>          # reinstall from the recorded source
oino update --extensions  # all recorded packages
oino config               # print the settings.json paths in play

Global installs live in ~/.oino/extensions/, project installs in <project>/.oino/extensions/ (trusted projects only). A user package with the same id as a distribution package shadows it.

Distribution packages (the ones the installer owns) are listed with scope dist and cannot be removed; they update with the binary. OINO_DIST_EXTENSIONS overrides their directory, and an empty value hides them, which is useful for testing with a clean slate.