Library Packages
A library package is an npm package that adds native firmware capability to a Cuttlefish project. You install it with npm install, import it like any other TypeScript module, and the transpiler resolves the import into C++ — a shim (a thin C++ adapter file) compiled into your firmware, plus the build fragments the framework needs. On Zephyr those fragments are a devicetree overlay (Zephyr’s hardware-description file) and Kconfig lines (Zephyr’s build-configuration symbols).
The first library package is @typecad/zephyr-esp32s3-rgb, which drives the onboard addressable RGB LED of the ESP32-S3 DevKitC — a WS2812, an addressable LED often sold as a “NeoPixel”. This page uses it twice: first as a consumer, then as a complete walkthrough for building your own.
Using a Library Package
You need a Cuttlefish project on the Zephyr framework with an ESP32-S3 DevKitC target (for example, one created with cuttlefish create --target esp32s3_devkitc).
Install the package.
npm install @typecad/zephyr-esp32s3-rgbImport it and call it like any other module.
import { rgbLed } from '@typecad/zephyr-esp32s3-rgb'; export function setup(): void { rgbLed.color(0, 255, 0).show(); // green rgbLed.color('#ff0000').show(); // red, CSS-style hex rgbLed.brightness(64).color('#ffffff').show(); // dim white rgbLed.off(); // black + show }Colors buffer locally. Nothing reaches the LED until
show()(oroff()).Compile and upload.
npm run compile npm run uploadWatch for
✓ Done— that is the signal the import resolved through the library. Four things landed in your project:#include "__tc_rgbled.h"in the generated source, with the shim files next to it.CONFIG_LED_STRIP=y,CONFIG_I2S=y,CONFIG_DMA=yappended toprj.conf.- A
ws2812node appended toboards/esp32s3_devkitc.overlay. - The shim compiled and linked into
zephyr.elflike any other source file.
A library declares the framework it targets. Importing a Zephyr library into a native (desktop) project stops the transpile with a message naming both frameworks — you find out in seconds, not at a C++ compiler error.
Finding Libraries
npm is the catalog — there is no separate registry to maintain. Every library package carries two keywords: the marker cuttlefish-library and one category keyword. The CLI scans the npm registry with those keywords.
Categories
| Category | Keyword | Covers |
|---|---|---|
| LEDs | cuttlefish-led | LEDs, addressable strips (WS2812-class), LED drivers |
| Displays | cuttlefish-display | TFT, OLED, e-ink panels and their drivers |
| Sensors | cuttlefish-sensor | Environmental, motion, IMU, light, distance |
| Actuators | cuttlefish-actuator | Motors, servos, steppers, relays |
| Communications | cuttlefish-comms | Radios and networking beyond built-in WiFi/BLE (LoRa, GSM, CAN add-ons) |
| Storage | cuttlefish-storage | SD cards, flash chips, FRAM |
| Audio | cuttlefish-audio | Microphones, amplifiers, codecs |
| Power | cuttlefish-power | PMICs, battery gauges, chargers |
| Input | cuttlefish-input | Keypads, encoders, add-on touch |
| I/O Expanders | cuttlefish-io | Port expanders, matrix drivers, 7-segment |
| Timing | cuttlefish-timing | RTCs, precision clocks, GPS time |
| Utility | cuttlefish-utility | Pure-code helpers with no hardware of their own |
Searching and Installing
Browse every library, or narrow by category or free text.
cuttlefish library search # everything published cuttlefish library search --category led # one category cuttlefish library search ws2812 # free text within librariesWatch for the package list with names, versions, category labels, and an installed marker.
--jsongives the machine-readable form.Install into your project from its root.
cuttlefish library install @typecad/zephyr-esp32s3-rgbWatch for
Done— the package is in your dependencies and ready to import. This is annpm installunder the hood, so version pinning and lockfiles behave exactly as you expect.
What’s in a Library Package
Five pieces, all ordinary files in one npm package:
| Piece | File | What it is |
|---|---|---|
| TypeScript API | src/index.ts | The typed surface you import. Never executed — it is the contract your editor sees. |
| Manifest | cuttlefish.library.json | Declares the import, the framework, and the native artifacts. |
| Shims | shims/*.h, shims/*.cpp | The C++ implementation, written into the generated sources. |
| Devicetree fragment | shims/*.overlay | Appended to the generated overlay when the library is used. |
| Kconfig | manifest kconfig | CONFIG_* lines appended to prj.conf when the library is used. |
The division of labor is the point. The library owns the code and the hardware facts. The framework owns the merge — it already knows how to write prj.conf and overlays for its build system. You never touch either.
Scaffolding a Library
You don’t have to build the five pieces by hand. cuttlefish library init generates a complete, valid package and you replace the stubs.
Scaffold from any directory.
cuttlefish library init @acme/led-ring --framework zephyr --category led --targets esp32s3_devkitcWithout flags it prompts: package name, framework, category, board targets. The scaffold lands in a directory named for the library id (
led-ring).Check what you got.
led-ring/ package.json # keywords, files, build script — publish-ready cuttlefish.library.json # the manifest, with Zephyr kconfig + overlay slots src/index.ts # the typed API skeleton shims/__tc_led_ring.h # AUTOSAR C++14 stub that compiles as-is shims/__tc_led_ring.cpp shims/led-ring.overlay # devicetree fragment skeleton (Zephyr) tests/library.test.ts # starter AUTOSAR-strict test over the shim bytes README.md # authoring checklistValidate as you go.
cuttlefish library validate .Watch for
Valid library package.— the validator checks the manifest schema, that every listed shim and overlay file exists, that the gate token appears in the include, that package.json carries the marker and category keywords, and that the shim bytes pass the AUTOSAR C++14 strict check. A fresh scaffold passes; each error message names the fix.
The walkthrough below fills in what each of these pieces means, using the RGB LED library as the worked example.
Walkthrough: the ESP32-S3 Onboard RGB LED
The native Zephyr path to this LED runs through a devicetree binding, pinmux wiring (the chip’s routing of controller signals to pins), DMA (Direct Memory Access, the hardware data mover), and three Kconfig symbols before the first blink. The library moves all of that into itself, paid once, so every project after it is three lines.
This walkthrough builds @typecad/zephyr-esp32s3-rgb from scratch.
1. Lay out the package
zephyr-esp32s3-rgb/
package.json # normal npm package, zero runtime dependencies
tsconfig.json
src/index.ts # the TypeScript API
cuttlefish.library.json # the manifest
shims/
__tc_rgbled.h # C++ class header
__tc_rgbled.cpp # C++ implementation
tc-rgb.overlay # devicetree fragmentIn package.json, ship the manifest and shims with the code:
{
"name": "@typecad/zephyr-esp32s3-rgb",
"version": "1.0.0",
"type": "module",
"files": ["dist", "src", "shims", "cuttlefish.library.json"]
}2. Write the TypeScript API
export class RgbLed {
color(r: number, g: number, b: number): this;
color(hex: string): this;
color(rOrHex: number | string, g?: number, b?: number): this {
return this;
}
brightness(scale: number): this { return this; }
show(): this { return this; }
off(): this { return this; }
}
export const rgbLed: RgbLed = new RgbLed();Two rules shape this file:
- Method names are load-bearing. The transpiler renders calls on imported objects verbatim —
rgbLed.color(0, 255, 0).show()appears in the C++ exactly as written. The C++ class in the shim must define the same names, and the shim defines a globalrgbLedinstance to match the export. - Export a singleton, not a constructor. A bare
new RgbLed()in user code renders as a C++ heap allocation, which the AUTOSAR C++14 coding standard (a strict embedded C++ ruleset the shims follow) forbids. A preconstructed instance sidesteps the heap entirely.
3. Declare the manifest
{
"id": "zephyr-esp32s3-rgb",
"module": "@typecad/zephyr-esp32s3-rgb",
"framework": "zephyr",
"targets": ["esp32s3_devkitc"],
"include": ""__tc_rgbled.h"",
"gateToken": "__tc_rgbled",
"shims": [
{ "path": "shims/__tc_rgbled.h", "outName": "__tc_rgbled.h" },
{ "path": "shims/__tc_rgbled.cpp", "outName": "__tc_rgbled.cpp" }
],
"kconfig": ["CONFIG_LED_STRIP=y", "CONFIG_I2S=y", "CONFIG_DMA=y"],
"overlay": "shims/tc-rgb.overlay"
}| Field | What it does |
|---|---|
module | The import specifier this library answers to. |
framework | The one framework it supports. A mismatch stops the transpile with a clear message. |
targets | Optional. Build-target prefixes; a build for a different board stops the transpile the same way. |
include | The #include line emitted for the import. |
gateToken | A token that must appear in the emitted sources before any artifact is written — an installed-but-unused library contributes nothing. |
shims | Files copied into the generated source directory. |
kconfig | Lines appended to prj.conf. Your own zephyr.kconfig overrides still win. |
overlay | A devicetree fragment appended to the generated overlay. |
A library package is framework-specific by design. A native counterpart of this library would be its own package with "framework": "native" and a different shim — the mechanism does not ask one package to serve every framework.
4. Write the shim
The shim is a plain C++ class. It buffers one pixel and pushes it with Zephyr’s LED-strip driver:
// __tc_rgbled.h
#ifndef TC_RGBLED_H_
#define TC_RGBLED_H_
#include <cstdint>
#include <zephyr/device.h>
#include <zephyr/drivers/led_strip.h>
class RgbLed final
{
public:
RgbLed& color(std::uint8_t r, std::uint8_t g, std::uint8_t b);
RgbLed& color(const char* hex);
RgbLed& brightness(std::uint8_t scale);
RgbLed& show();
RgbLed& off();
private:
struct led_rgb pixel_{};
std::uint8_t scale_ = 255U;
};
extern RgbLed rgbLed;
#endif // TC_RGBLED_H_// __tc_rgbled.cpp (the one line that touches hardware)
RgbLed& RgbLed::show() {
struct led_rgb out{};
out.r = channel_scaled(pixel_.r, scale_);
out.g = channel_scaled(pixel_.g, scale_);
out.b = channel_scaled(pixel_.b, scale_);
static_cast<void>(led_strip_update_rgb(
DEVICE_DT_GET(DT_ALIAS(led_strip)), &out, 1U));
return *this;
}Shims must pass --autosar=strict — fixed-width integers, static_cast instead of C-style casts, no heap, final on leaf classes. Write them that way from the first line; the compliance check treats library shims exactly like framework shims.
5. Ship the devicetree fragment
The overlay fragment describes the LED to Zephyr: a WS2812 on GPIO48, clocked by the I2S0 peripheral (the chip’s digital-audio bus, which happens to be a precise waveform generator), wired in the part’s GRB channel order (green-red-blue — the order the LED samples color bytes off the wire):
/ {
aliases {
led-strip = &led_strip;
};
};
&i2s0_default {
group1 {
pinmux = <I2S0_O_SD_GPIO48>;
};
};
i2s_led: &i2s0 {
status = "okay";
dmas = <&dma 3>;
dma-names = "tx";
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-i2s";
reg = <0>;
chain-length = <1>;
color-mapping = <2 1 3>; /* GRB */
reset-delay = <500>;
};
};This fragment is adapted from Zephyr’s own sample overlay for this board, which is the right instinct: when upstream shows the canonical wiring for a part, copy it and say so in a comment. The hardware fact worth documenting is the pin — DevKitC v1.0 puts the LED on GPIO48, v1.1 moved it to GPIO38, and a revision swap is a one-line change to the pinmux.
6. Publish and install
The package has zero runtime dependencies — the transpiler reads the manifest and the shims as files. Publish with npm publish, and anyone with a matching project can npm install it and import it. Because the package.json carries the marker (cuttlefish-library) and category (cuttlefish-led) keywords, it appears in cuttlefish library search the moment it is indexed. Until it is published, a workspace or file: dependency works the same way.
How the Transpiler Wires It Up
⚙️ Advanced details — registration, gating, and the sidecar
Nothing scans node_modules. When the transpile graph resolves an import and the package root contains cuttlefish.library.json, the package registers at that moment and its TypeScript is skipped — the package’s own type declarations are the compile-time contract. The import then emits the manifest’s include instead of a transpiled-module header.
After codegen, the transpiler scans the emitted sources for each registered library’s gateToken. For every library whose token is present, it writes the shims next to the emitted sources and records the library in a libraries.json sidecar — the same convention as the board-constants sidecar. The framework’s build step reads that sidecar to merge kconfig lines into prj.conf and append the overlay fragment to the generated overlay. Removing the import removes the shims on the next build; an installed library that is never imported writes nothing.
Checklist for a New Library
- One framework per package; name it for the framework and the part (
zephyr-esp32s3-rgb). - TypeScript method names match the C++ shim names exactly; calls render verbatim.
- Export a singleton instance, not a constructor.
- Shims pass
cuttlefish library validate(AUTOSAR C++14, strict) from the first line. filesinpackage.jsonincludes the shims and the manifest.- Keywords include the marker (
cuttlefish-library) and one category (cuttlefish-<id>). - Document the board facts you carry — pin, hardware revisions, which peripheral the library borrows (this one uses I2S0 and a DMA channel, which nothing else in a Cuttlefish Zephyr project claims).
- When upstream firmware SDKs show a canonical wiring for your part, adapt their overlay and credit it in a comment.
On This Page