Skip to content

Armaell/owlbrain-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OwlBrain starter

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.

Quickstart

Install

git clone https://github.com/armaell/owlbrain-starter
cd owlbrain-starter
npm install

Run

Directly in the console

npm run dev

You can then Open scripts/example.script.ts and change the content. dev script will auto-restart on change.

Using docker

docker build -t owlbrain .
docker run -d --name owlbrain owlbrain

or

docker compose up --build -d

Understanding what you have

The scripts

Open 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.

The Event decorator

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.

Using integrations

Integrations provide connection to services and new event decorators enable you to do more.

Official integrations

// TODO put correct links here

  • owlbrain-http — Allow to listen to http calls on your application
  • owlbrain-homeassistant — Connect to Home Assistant

Enable an integration

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()
  }
}

Examples

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>

Details

The Core

You use it to configure and start OwlBrain, maybe stop it and nothing more. It then run by itself.

How to start

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.

Concurrency

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.

OwlEvent

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.

Decorators

@Script()

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

Making a script injectable

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
}

@OnEvent()

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.

Usage

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

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.

Avoiding conflicts

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

Logger

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.

Use the logger

const logger = new Logger(["script", "my-super-script"])
logger.info("1,2, 3")

Configure levels visibility

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

Lifecycle @OnInit(), @OnStart(), @OnStop()

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

Scheduled events @Schedule

This allows scripts to run methods automatically at specific times.

Two ways to write it:

  1. Using 6-field cron expression:
@Schedule.cron("0 0 1 * * *")
async generateDailyReport(event: OwlEvent) {
  console.log("Generating daily report…")
}
  1. 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

@OnlyIf()

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
  }
}

@Delay()

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
  }
}

About

Starter pack for OwlBrain — Event-driven engine built for writing home automations in typescript

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors