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

@sunreye/profile-sdk

v2.0.1

Published

Authoring SDK + CLI for SunReye inverter profiles: define, validate, scaffold, and exercise ProfileData without hardware.

Readme

@sunreye/profile-sdk

Authoring SDK + CLI for SunReye inverter profiles: define a profile as typed code, compile it to serializable ProfileData, then validate, score, and exercise it — all offline, no hardware required.

A profile is pure data. It can fail validation or produce an empty manifest, but it can never execute code inside SunReye.

Quick start

Scaffold a ready-to-build authoring project in one command — no install needed, works in an empty directory:

bunx @sunreye/profile-sdk init my-profiles

It writes the project layout (package.json, tsconfig.json, src/profiles.ts with a starter profile, README.md, .gitignore) and optionally runs bun install + git init, leaving you one bun run build away from an installable repo. See Start a new profile project for flags.

Install

bun add -d @sunreye/profile-sdk

Define a profile

import { defineProfile, metric } from "@sunreye/profile-sdk";

export const acme = defineProfile({
  id: "acme-hybrid",
  name: "Acme Hybrid",
  manufacturer: "Acme",
  version: "1.0.0",
  metrics: [
    metric("dc/pv1/power", {
      label: "PV1 Power", group: "solar",
      role: "pv.string.power", index: 1, addr: 672, unit: "W",
    }),
    metric("battery/soc", {
      label: "Battery SoC", group: "battery",
      role: "battery.soc", addr: 588, unit: "%",
    }),
  ],
});

Picking a role forces you to supply exactly what that role needs, or the code won't compile — profiles are correct by construction.

Families & model variants

One repo can hold hundreds of profiles (index.json + one profiles/<id>.json per model). When several models share a register map and differ only in a few limits or PV inputs, author them as a family instead of copying the map. defineFamily takes the shared base plus a models record keyed by profile id, and returns [base, ...models] — each a self-contained profile that shows up in SunReye's model picker:

import { defineFamily, metric } from "@sunreye/profile-sdk";

export const acme = defineFamily({
  id: "acme-hybrid",
  name: "Acme Hybrid",
  manufacturer: "Acme",
  version: "1.0.0",
  metrics: [ /* the shared register map — 2 PV strings, a writable discharge limit, … */ ],
  models: {
    "acme-hybrid-5k": {
      name: "Acme Hybrid 5K",
      metrics: {
        "dc.pv2.*": null,                                            // one MPPT → capabilities show 1
        "settings.battery.maximum_discharge_current": { max: 120 },  // 0–120 A slider + write clamp
      },
    },
    "acme-hybrid-15k": {
      name: "Acme Hybrid 15K",
      metrics: { "settings.battery.maximum_discharge_current": { max: 280 } },
    },
  },
});

The models[id].metrics overlay is keyed by canonical metric key with one rule per entry:

| Entry | Effect | | --- | --- | | "key": { max: 280 } (or min/any metric() field) | patch — merge into that metric; min/max set its range | | "key": null | remove that metric | | "prefix.*": null | remove every metric under the prefix (e.g. a PV string) | | "new.key": { …full definition… } | add a new metric (topic derived from the key) |

Keys autocomplete from the base map; a mistyped patch/remove target throws at build time (profile build validates every profile). Because capabilities are derived — string count, phases, genset presence — removing metrics reshapes the manifest and UI automatically, and a range on a writable setting becomes a capped slider that also rejects out-of-range writes server-side. No schema, hydrate, or server change is involved. defineVariant(base, { id, … }) is the low-level primitive for specializing a single imported/third-party ProfileData.

Deferred aggregates + safe removal

Removing a metric another one references is reconciled automatically. A removed key in a variadic compute list (sum, combine.add/sub, ratio.num/den) is pruned; a removed key in a fixed-arity expr (diff/scale), one that would empty a required list, or a control target, throws at build time naming both metrics — never a silent wrong value. Even better, sumOf lets the base declare the intent once instead of a hand-listed key set that drifts:

import { metric, sumOf } from "@sunreye/profile-sdk";

metric("dc/total_power", {
  label: "PV Total", group: "solar", unit: "W",
  role: "pv.total.power",
  computeExpr: sumOf({ role: "pv.string.power" }), // or sumOf({ keyPrefix: "battery.bank" })
});

It resolves to a concrete { sum: [...] } against each model's surviving metrics at build time (matching zero metrics is a build error), so a model that adds or drops a PV string re-derives the correct total with no per-model patch.

Validate and exercise

import { validateProfile, coverage, exerciseProfile } from "@sunreye/profile-sdk";

const result = validateProfile(acme); // strict schema + semantic lints
const report = coverage(acme); // which canonical roles are mapped

// Run the profile end to end against the built-in simulator:
const { manifest, capabilities, sample } = await exerciseProfile(acme);

Start a new profile project

bunx @sunreye/profile-sdk init my-profiles    # interactive: package + first profile stub, then optional install + git init

init scaffolds a ready-to-build authoring project (package.json, tsconfig.json, src/profiles.ts with a starter profile, README.md, .gitignore), asks whether to run bun install and git init, and leaves you one bun run build away from an installable repo. Run it via the package name (bunx @sunreye/profile-sdk) so it works in an empty directory before anything is installed; inside the scaffolded project the profile bin is on the path. Pass flags to skip prompts, or --yes to accept every default:

bunx @sunreye/profile-sdk init my-profiles --id acme-hybrid --manufacturer Acme --repo-name "Acme Profiles" --yes

The profile CLI

bunx @sunreye/profile-sdk init ./my-profiles   # scaffold a new authoring project (empty dir; no install needed)
bunx profile upgrade                            # refresh the AI authoring guide (AGENTS.md + CLAUDE.md) in an existing project
bunx profile validate ./profiles/acme.json     # strict validation + lints, non-zero exit on failure
bunx profile coverage ./profiles/acme.json     # role coverage report + sumOf optimization hints
bunx profile scaffold ./registers.csv --id acme-hybrid --name "Acme Hybrid" --manufacturer Acme
bunx profile build ./src/profiles.ts --out . --name "My Profiles"   # emit installable repo

profile upgrade re-syncs the AI guide files into a project scaffolded before they existed; it keeps any file you've edited (re-run with --force to overwrite). profile coverage also flags hand-listed sums that exactly cover an indexed role group and suggests the self-healing sumOf form — a non-destructive hint, never a rewrite.

validate and build exit non-zero on failure, so they work as a CI or pre-commit gate.

Build an installable profile repo

SunReye installs profiles at runtime from any public git repo containing an index.json plus one ProfileData JSON file per profile. profile build generates exactly that layout from your code-defined profiles:

bunx profile build ./src/profiles.ts --out . --name "My Profiles" --maintainer you

Every export of the entry module that is a profile (or a { profile, description } wrapper, or an array of either) is validated and written to profiles/<id>.json, and index.json is regenerated — commit, push, and the repo is installable from Settings → Profiles. Any invalid profile or duplicate id fails the whole build.

Same thing programmatically:

import { buildRepo } from "@sunreye/profile-sdk";

const { ok, issues, files } = buildRepo(
  [acme, { profile: zeta, description: "Zeta 3–6 kW range" }],
  { name: "My Profiles" },
);
// files: { "index.json": "...", "profiles/acme-hybrid.json": "...", ... }

See the authoring guide and distribution guide.

License

AGPL-3.0-only