npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enody/enody

v0.1.0

Published

JavaScript SDK for Enody spectrally tunable lighting devices and EP01 firmware updates.

Readme

enody

JavaScript SDK for Enody spectrally tunable lighting devices.

This is an LLM port of the enody-py framework, with modifications to support browser and node backends.

@enody/enody provides:

  • One import path that works in both the browser and Node.js
  • WebSerial-based discovery and control for EP01 hardware in browser apps
  • Node-based serial discovery and control for scripts, CLIs, and notebooks
  • A device hierarchy that mirrors the Rust and Python SDKs
  • Bundled sample spectral data for offline development
  • Optimization helpers for spectral matching and chromaticity work
  • Firmware update support for EP01 devices

The browser dashboard that consumes this SDK lives in the sibling device-dashboard project.

Installation

npm install @enody/enody

The package targets modern ESM environments and requires Node 18+ for local tooling.

import { UsbEnvironment, Configuration, Flux } from '@enody/enody' resolves to the appropriate implementation automatically:

  • in Node.js, @enody/enody uses the Node serial backend
  • in browser builds, @enody/enody uses WebSerial

That means the same import works in browser apps, scripts, and Node-backed notebooks.

Quick Start

Discover and connect to a device

import { UsbEnvironment } from '@enody/enody';

const environment = new UsbEnvironment();
const runtimes = await environment.runtimes();
const runtime = runtimes[0];
const host = await runtime.host();

console.log(`Host ${host.identifier()} (v${host.versionString})`);

const fixtures = await host.fixtures();
const sources = await fixtures[0].sources();
const emitters = await sources[0].emitters();

Use the same import in Node or a notebook

import { UsbEnvironment, Configuration, Flux } from '@enody/enody';

const env = new UsbEnvironment();
const runtime = (await env.runtimes())[0];
const host = await runtime.host();
const fixture = (await host.fixtures())[0];

await fixture.display(
  Configuration.blackbody(4000),
  Flux.relative(0.8),
);

When multiple serial devices are present in Node, set ENODY_PORT to target a specific port path.

Control a fixture

import { Configuration, Flux } from '@enody/enody';

await fixtures[0].display(
  Configuration.blackbody(4000),
  Flux.relative(0.8),
);

Convenience helpers are also available:

await fixtures[0].setCCT(4000, 0.8);
await fixtures[0].setChromaticity(0.3127, 0.3290, 0.8);
await fixtures[0].setManual(1.0);

Manage stored appearance presets

import { Configuration } from '@enody/enody';

const presets = await runtime.configurationPresets();

presets.push(Configuration.blackbody(2700));

await runtime.setConfigurationPresets(presets);

runtime.configurationPresets() reads the same runtime setting used by the Rust host tooling for EP01 appearance presets.

Pair EP01 with WiFi

WiFi setup runs over an already trusted USB/WebSerial connection:

const host = await runtime.host();
const networks = await host.wifiScan();

await host.wifiJoin('Studio WiFi', 'network-password');

Pass an empty password for open networks. Lower-level networkScan() and networkJoin() helpers are also exported for non-WiFi network variants.

Enable SDK diagnostics

Transport diagnostics are quiet by default. During hardware bring-up or support sessions, pass a console-like logger or debug: true:

const environment = new UsbEnvironment({ logger: console });

Command timeouts reject with code: 'ENODY_COMMAND_TIMEOUT', and device errors include deviceError and command fields for application-level handling.

Work offline with bundled sample data

import { sampleFixture, sampleSource, sampleEmitter } from '@enody/enody';

const fixture = sampleFixture();
const source = sampleSource();
const emitter = sampleEmitter();

console.log(fixture.identifier(), source.identifier(), emitter.identifier());

Use the optimizer helpers

import {
  SpectralOptimizer,
  cieXAction,
  cieYAction,
  cieZAction,
  sampleSource,
} from '@enody/enody';

const source = sampleSource();
const emitters = await source.emitters();

const spdMatrix = new Float32Array(emitters.length * 401);
for (let emitterIndex = 0; emitterIndex < emitters.length; emitterIndex += 1) {
  const values = (await emitters[emitterIndex].spectralData()).values();
  spdMatrix.set(values, emitterIndex * 401);
}

const optimizer = new SpectralOptimizer({
  spdMatrix,
  numEmitters: emitters.length,
  cieX: new Float32Array(cieXAction()),
  cieY: new Float32Array(cieYAction()),
  cieZ: new Float32Array(cieZAction()),
});

optimizer.setTargetChromaticity(0.3127, 0.3290);
optimizer.step();

Update firmware

import { UpdateTarget } from '@enody/enody';

const [target] = await UpdateTarget.discover();
const versions = await target.availableFirmware();

await target.updateDevice(versions[0], {
  onLog: console.log,
});

By default the updater fetches manifests from https://firmware.enody.lighting. In local development you can point it at a proxy:

const [target] = await UpdateTarget.discover({
  baseUrl: '/firmware',
});

API Overview

The primary resource hierarchy follows the native SDKs:

UsbEnvironment -> Runtime -> Host -> Fixture -> Source -> Emitter

Key exports:

  • UsbEnvironment, Runtime, Host, Fixture, Source, Emitter
  • Configuration, Flux, Version
  • Network, NetworkCredentials, WifiAuth, WifiNetwork
  • describeCommand, ErrorType, errorTypeName
  • CONFIGURATION_PRESETS_KEY
  • sampleFixture, sampleSource, sampleEmitter
  • SpectralOptimizer, GPUCompute, computeChromaticity, computeSSI
  • UpdateTarget, ESPFlasher

Development

Run the SDK tests from the package directory:

npm test

Run the optional Node hardware-in-the-loop smoke test with a connected EP01:

npm run test:hil

To force a specific serial device in Node:

ENODY_PORT=/dev/tty.usbmodem1234 npm run test:hil

The sibling demo application can be served with:

cd /Users/carter/llm_sandbox/src/js-sdk/enody-web
python3 server.py 8080