Building extensions
A walkthrough of how a Foxora extension is structured, how to develop one locally, and how to publish it to the hub.
Public SDK in beta
The extension SDK is in private beta. The shape below is stable for the beta program but may evolve before public release. Email builders@foxora.ai to request access.
Anatomy of an extension
An extension is a folder with three files at minimum:
bash
my-extension/
├─ manifest.json # name, version, permissions, exports
├─ index.js # tool / trigger handlers
└─ README.md # description shown in the hubmanifest.json
json
{
"name": "weather",
"version": "0.1.0",
"description": "Look up the weather for a city.",
"author": "your-handle",
"permissions": ["network"],
"tools": [
{
"id": "weather.lookup",
"label": "Look up weather",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
]
}index.js
Each tool the manifest declares maps to a handler exported from index.js. Handlers are async and receive the validated input plus a small ctx object.
js
export const tools = {
"weather.lookup": async ({ city }, ctx) => {
const res = await ctx.fetch(
`https://api.weather.example.com/v1/now?q=${encodeURIComponent(city)}`
);
const data = await res.json();
return { temp_c: data.temp, summary: data.summary };
},
};Run it locally
- Install the CLI:
npm i -g @foxora/extensions-cli(beta) - From your extension folder:
foxora-ext dev - Ember OS picks up the local extension automatically and shows it under Extensions → Local.
- Trigger the tool from a chat or with
foxora-ext invoke weather.lookup --input city=Berlin.
Publishing
- Bump the version in
manifest.json. - Run
foxora-ext publish. - The CLI uploads the bundle and opens a review request in the hub.
- Foxora reviews for safety + permissions and publishes within a few business days.
Keep the surface small
The best extensions expose one or two well-named tools that do exactly one thing each. Agents are much better at picking the right tool when there are fewer of them.