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:
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:
{
"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:
{
"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:
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:
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 playGlobal 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.