OwlBrain is an event‑driven automation engine that keeps the simplicity of Home Assistant–style triggers while unlocking the full expressive power of TypeScript for complex logic.
While No‑code or visual ECA builders (like Home Assistant’s UI automations or Node‑RED flows) excel at simple rules. But as soon as your logic grows, they buckle under their own weight.
OwlBrain is built for the other half of automation — the part where things get interesting and magic.
- Events stay familiar—triggers, state changes, schedules, webhooks, events.
- Full code — for real logic, branching, composition, and reuse.
- Automation stays maintainable — no visual spaghetti, no YAML nesting hell, no flow‑chart bloat.
git clone https://github.com/armaell/owlbrain-starter
cd owlbrain-starter
npm installnpm run devYou can then Open scripts/example.script.ts and change the content. dev script will auto-restart on change.
docker build -t owlbrain .
docker run -d --name owlbrain owlbrainor
docker compose up --build -dOpen the `scripts/ folder and see your first script.
Scripts are the smallest unit of behavior. They are simple TypeScript classes that react to events, perform work, and orchestrate integrations.
A script is:
- Instantiated by the runtime
- Activated by event decorators that declare what the script reacts to
- Isolated and modular, making it easy to reason about and test
You can find a the default example you get when starting a new owlbrain project in the scripts/ folder:
import { Script, OnStart, OwlEvent } from "owlbrain-core";
@Script()
export class HelloWorld {
@OnStart()
async onStart(event: OwlEvent) {
console.log("Hello world!");
}
}Scripts are long-lived, you can use it to hold state and information through events.
let count = 0
@OnEvent("motion.bedroom")
async onMotion(event: OwlEvent) {
this.count++
console.log("Bedroom motion count:", this.count)
}Of course those state are not kept between restarts unless you ensure it yourself.
Event decorators are how scripts declare what they react to. They allow to easily react to events without having to bother with subscriptions. Everything comes pre-wired.
import { Script, OnEvent, OnStart, Logger } from "owlbrain-core"
import type { OwlEvent } from "owlbrain-core"
@Script()
class BedroomMorning {
@OnEvent("time.07:00")
async onMorning(event: OwlEvent) {
// Turning light one gently to wake the occupant
}
@OnEvent("motion.bedroom")
async onEarlyWake(event: OwlEvent) {
// occupant woke-up early, opening the blinds
}
}This example is purely conceptual ; actual events will be provided by new event decorators from integrations. owlbrain-core by itself is actually a little bland.
Integrations provide connection to services and new event decorators enable you to do more.
// TODO put correct links here
- owlbrain-http — Allow to listen to http calls on your application
- owlbrain-homeassistant — Connect to Home Assistant
Install it using npm or your preferred package manager.
import { OwlBrain } from "owlbrain-core";
import { HttpIntegration } from "owlbrain-http";
await OwlBrain.withIntegration(HttpIntegration({...})).run()You can now use the event decorators provided by the integration.
By example with home assistant:
@EntityScript({ entity_id: "motion.bedroom" })
class LightAutomation {
const dressingLight = lightEntity("light.dressing")
@OnStateChange({ to: "on" })
async onMotion(event: OwlEvent) {
await this.dressingLight.turnOn()
}
}You can find more examples in the examples/ folder of each packages, core or integrations.
You can also try them if you checkout each package :
- To list them :
npm run example - To start one :
npm run example <example-name>
You use it to configure and start OwlBrain, maybe stop it and nothing more. It then run by itself.
await OwlBrain
.withIntegration(...)
.withScriptsPath("scripts/")
.run()The core is started on start() or run() call. The only difference is that run() returns a promise that will be resolved only when the core is stopped.
If you uses start() you can then also call wait() for the same effect.
The events are processed sequentially by default. You can enable parallel executions:
OwlBrain.withWorkersCount(4)This lets multiple events be handled at the same time.
Note
Event with concurrency enable, only one event is run per script at the same time.
All event decorators will pass along an event object, the OwlEvent is its most basic form.
It contains :
- name—the event identifier, can be any string
- namespace—an optional grouping that is generally the integration name origin. It prevent name collision
More specialized event type provided by integrations will contains more information.
A script decorator marks a class as an automation script. OwlBrain discovers these classes at startup, instantiates them once, and wires their event‑decorated methods so they can react to triggers.
Integrations may provide more specialized script decorators. @Script is the most basic version of script decorator
import { Script, OnStart, OwlEvent } from "owlbrain-core"
@Script()
export class HelloWorld {
@OnStart()
async onStart(event: OwlEvent) {
console.log("Hello world!")
}
}Tip
You can use a script decorator multiple time on the same class, it will be instantiated as many time. Useful if you want to run multiple script with the same behavior but maybe with difference script configuration
A good way to allow cross-script calls is by injecting a script into another. This is enabled by the injectableAs parameter of the @Script decorator, and the @Inject decorator.
@Script({ injectableAs: ["script", "my-super-script"] })
export class MySuperScript {
// ...
}Then you can retrieve it in another script:
@Script()
export class MyOtherScript {
@Inject(["script", "my-super-script"])
private superScript!: MySuperScript
}event decorators connects a method to the event bus. This is the main way scripts respond to triggers coming from integrations, schedules, sensors, or other scripts.
Integrations will provide more complex events decorators tailored with for their own events, and typed with own OwlEvent objects.
@OnEvent is the most basic, and you will probably only use it if you want a dirty peek at the event bus.
There is two way to use it:
Only passing the event name, and it will trigger on any event with the same name:
@OnEvent("motion.bedroom")
async onBedroomMotion(event) {
// run when motion is detected in the bedroom
}Using a filter function for more advanced cases:
@OnEvent(event => event.namespace === "http" && event.name === "webhook")
async onWebhook(event) {
// react only to specific HTTP webhook events
}Integrations are how OwlBrain connects to the outside world. They provide new event sources, new decorators, and optional services your scripts can use. Each integration plugs into the core at startup and extends what your automations can react to.
Each integration has a unique namespace. While this is used to prevent mixing event name between two integrations, this an also be used two have multiple time the same integration.
By example if you want to open HTTP endpoints on multiple ports :
await OwlBrain
.withIntegration(HttpIntegration({port: 80}, name: "http"))
.withIntegration(HttpIntegration({port: 443}, name: "https"))
.run()Event will then be prefixed by the relevant namespace depending on their origin
The logger provides a simple and unified per‑namespace logs. The Logger also share the concept of namespaces to easily understand who is currently logging.
const logger = new Logger(["script", "my-super-script"])
logger.info("1,2, 3")Log levels cascade by namespace. The most specific match wins:
"core.eventbus""core"""(default)
Configured through:
OwlBrain.withLoggerLevels({
"": "INFO",
"core": "WARN",
"core.eventbus": "DEBUG"
})This allow precise control on logs content.
Warning
a DEBUG level on the core, or the event bus in particular can be useful for debugging, but it can also be very noisy
These decorators let a script react to OwlBrain’s own lifecycle events. They allow to setup code, start logic when the engine is ready, or clean up before shutdown.
- @OnInit() — runs when the script is first registered and initialized, before the engine starts and integrations start producing events.
- @OnStart() — runs once OwlBrain and integrations have fully started and is ready to handle events.
- @OnStop() — runs when OwlBrain begins shutting down, useful for cleanup or saving state.
Each decorator maps to an internal lifecycle event emitted under the core.lifecycle namespace
This allows scripts to run methods automatically at specific times.
Two ways to write it:
- Using 6-field cron expression:
@Schedule.cron("0 0 1 * * *")
async generateDailyReport(event: OwlEvent) {
console.log("Generating daily report…")
}- Using natural language expression:
@Schedule.text("every day")
async generateDailyReport(event: OwlEvent) {
console.log("Generating daily report…")
}More examples :
- "every 5 minutes"
- "every day at 3pm"
- "every Monday and Friday at 09:30"
- "every 10 seconds"
- "every hour"
- And more in later.js documentation
This is a utility decorator.
@OnlyIf() allows to apply another restriction to an event handler call.
import { Script, OnEvent, Delay } from "owlbrain-core"
@Script()
class HighTempAlertScript {
readonly limit = 5
@OnEvent("some-event")
@OnlyIf((event, script) => event.data > script.limit)
onLimitReached(event) {
// method only called if event data is higher than the script limit of 5
}
}This is a utility decorator.
@Delay() postpones a method’s execution by a fixed number of milliseconds.
import { Script, OnEvent, Delay } from "owlbrain-core"
@Script()
class Example {
@OnEvent("motion.bedroom")
@Delay(2_000)
async handleMotion(event) {
// runs 2 seconds after the event
}
}