Quick start
Scaffold an integration, build it, run it against a development hub, and publish.
Scaffold
npm i -D @novon/integration-sdk esbuild socket.io-client typescript
npx novon-integration init my-thing --domain x_acme_thing --name "Acme Thing"
cd my-thing && npm installThe CLI writes manifest.json, src/index.ts, tsconfig.json and package.json from its template.
Domains are x_<vendor>_<name>: lowercase letters, digits and single underscores, unique across the registry.
Build
npx novon-integration build # → dist/integration.js + dist/manifest.jsonThe CLI bundles src/index.ts with esbuild and validates the manifest with the same schema the
registry and the hub use.
Run against a development hub
npx novon-integration dev --watch # builds into ./.novon-dev-integrations/x_acme_thing
NODE_ENV=dev NOVON_DEV_INTEGRATIONS_DIR=$PWD/.novon-dev-integrations COMMUNITY_INTEGRATIONS_ALLOW_UNSIGNED=1 npm run start:edge-runtimeThe integration appears in the app's Add integration picker for that hub. Bundles load at runtime boot, so restart the runtime after a rebuild.
Publish
Needs an application API key with the communityIntegration:publish scope (minted in the admin
console under Settings → API keys):
export NOVON_ADMIN_API_KEY=nvk_… # or ~/.config/novon/admin-api-key
npx novon-integration publish --api-url https://stage-admin-api.novon.com --notes "first release"A (domain, version) is published once and is immutable: bump version in manifest.json for
every change. See Publishing.
A minimal integration
/// <reference types="@novon/integration-sdk/globals" />
import type { IntegrationDefinition } from '@novon/integration-sdk';
const definition: IntegrationDefinition = {
async setupEntry(ctx) {
const host = String(ctx.entry.data.host);
const first = await novon.http(`http://${host}/status`).catch(() => null);
if (!first?.ok) throw ctx.notReady(`Cannot reach ${host}`); // hub retries with backoff
await ctx.entities.add([{
uniqueId: `${host}:power`, platform: 'switch', name: 'Power', state: 'off',
device: { uniqueId: host, manufacturer: 'Acme', model: 'Thing' },
async onService(service) {
await novon.http(`http://${host}/power`, { method: 'POST', body: { on: service === 'turnOn' } });
ctx.entities.update(`${host}:power`, { state: service === 'turnOn' ? 'on' : 'off' });
},
}]);
setInterval(async () => {
const res = await novon.http(`http://${host}/status`);
ctx.entities.update(`${host}:power`, { state: res.json<{ on: boolean }>().on ? 'on' : 'off' });
}, 30_000);
},
};
novon.defineIntegration(definition);The /// <reference types> line gives you novon, console and the timers without importing
anything; the bundle must not import at run time because the hub injects those globals.