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

@code-all/harmonyai

v0.1.0

Published

The Harmony AI chat widget for your site — one call, framework agnostic, with React, Vue and Angular adapters.

Downloads

79

Readme

@code-all/harmonyai

The Harmony AI chat widget, as an npm package. One call to load it, a typed API you can use before it has finished loading, and thin adapters for React, Vue and Angular.

npm install @code-all/harmonyai

You need a widget key. It is in the Harmony merchant panel under Widgets, and it looks like wgt_…. The key is public — it ends up in your page source either way, and it grants access to nothing.


Quick start

import { harmony } from "@code-all/harmonyai";

harmony.load({ key: "wgt_YOUR_KEY", locale: "tr" });

That is the whole integration. The launcher appears; the chat itself is not loaded until the visitor opens it.

If all you want is a script tag, you do not need this package — <script src="https://widget.chatharmony.ai/embed.js?key=wgt_…" async></script> does the same job. What the package adds is a queue, and that matters the moment your app is a single-page app:

// Somewhere in your login handler, possibly before the widget has loaded.
harmony.setUser({ email: user.email, name: user.name });

With a bare script tag that line is window.Harmony?.setUser(…), which does nothing at all on the page loads where the script was still in flight — and tells you nothing when it does. Here it is queued and delivered when the widget arrives.


The API

| | | |---|---| | load(options) | puts the widget on the page. Idempotent; a no-op on the server | | ready() | a promise that resolves when the widget is loaded | | open() close() toggle() isOpen() | the panel | | setUser(user \| null) | sign-up prefill — see below | | setLocale(locale) setTheme(theme) | theme is light, dark or auto | | on(event, fn) | returns an unsubscribe function | | getState() subscribe(fn) | the reactive slice: { isReady, isOpen, unread } | | destroy() | removes the widget entirely | | config | the resolved boot config, or null before loading |

Events: open, close, ready, and unread with { count }.

const off = harmony.on("unread", ({ count }) => {
  document.title = count ? `(${count}) Shop` : "Shop";
});

load options

harmony.load({
  key: "wgt_YOUR_KEY",   // required
  locale: "tr",
  theme: "auto",
  origin: "https://widget.chatharmony.ai",   // staging or local, if you need it
});

Staging, and your local machine

There is one package, not one per environment — nothing about the environment is baked into it. origin chooses which Harmony the widget talks to, at runtime:

harmony.load({
  key: import.meta.env.VITE_HARMONY_KEY,
  origin: import.meta.env.VITE_HARMONY_ORIGIN, // omit for production
});

| | | |---|---| | production | omit origin | | staging | https://dev-widget.chatharmony.ai — with a key issued there |

A key belongs to one environment. A production key does not resolve against staging, and the widget removes itself rather than rendering something that cannot work.

setUser is prefill, not sign-in

Only email and name are read. They fill in the sign-up form's initial values so a visitor already signed in to your site does not retype their address.

They are not an identity. The values come from JavaScript in a browser, where anyone can change them, so nothing is authorized on their basis, they are never tied to an account or a conversation, and they never appear in a URL. If the visitor is already signed in to the widget they are ignored — there is no form to fill.

They are personal data, so do not call this before your consent banner has been accepted. Pass null on sign-out.


React

import { Harmony, useHarmonyState } from "@code-all/harmonyai/react";

// Once, at the root of your app.
<Harmony
  config={{ key: "wgt_YOUR_KEY", locale: "tr" }}
  user={user ? { email: user.email, name: user.name } : null}
/>;

<Harmony> renders nothing. It loads the widget on mount and calls setUser when the user prop changes; user={null} clears the prefill, and omitting the prop entirely means the component does not manage the user at all.

const { isOpen, unread } = useHarmonyState();

The hook is for rendering — a badge in your own navigation, a button that reflects the panel. To call the API, import harmony directly; there is no hook for that and there does not need to be, because it is a singleton.

There is no Provider, on purpose. Nothing is scoped to your component tree: the widget renders into document.body and there is exactly one of it.

Server components are fine — the React entry carries "use client", and the core is SSR-safe on its own.

Unmounting <Harmony> does not remove the widget. It belongs to the page, not to your component, and React StrictMode remounts components in development — a destroying cleanup would close an open panel and cut a streaming reply every time. Call harmony.destroy() when you actually mean it.

Vue 3

<script setup lang="ts">
import { Harmony, useHarmony } from "@code-all/harmonyai/vue";

const { unread, isOpen } = useHarmony();
</script>

<template>
  <Harmony :config="{ key: 'wgt_YOUR_KEY', locale: 'tr' }" :user="user" />
</template>

useHarmony() returns refs and releases its subscription with the component scope. Same rules as React: no plugin, no provide/inject, and unmounting does not destroy the widget.

Angular

import { provideHarmony, HarmonyStore } from "@code-all/harmonyai/angular";

bootstrapApplication(App, {
  providers: [provideHarmony({ key: "wgt_YOUR_KEY", locale: "tr" })],
});

export class Nav {
  private harmony = inject(HarmonyStore);
  unread = this.harmony.unread; // Signal<number>
  open = () => this.harmony.api.open();
}

state, isReady, isOpen and unread are signals. The adapter ships no decorators and no templates — there is nothing here for the Angular compiler to process, which is why the package needs no ng-packagr and does not care which Angular version you are on beyond signals existing (16+).

Svelte, Solid, and everything else

No adapter, and none is coming — the core is already what you need. subscribe(fn) calls fn immediately with the current state and again on every change, which is the Svelte store contract exactly:

<script>
  import { harmony } from "@code-all/harmonyai";
  harmony.load({ key: "wgt_YOUR_KEY" });
</script>

<span>{$harmony.unread}</span>

Anything else binds to subscribe and getState in three lines.


Server-side rendering

load() and every other method are no-ops when there is no window, and nothing touches document at import time. Next.js, Nuxt and Angular Universal all work without a dynamic import or a typeof window check on your side.

Consent, and removing the widget

destroy() removes the element, drops every listener and deletes the global. Calling load() afterwards starts a fresh widget. That is the rollback path for a visitor who withdraws consent; the safer path is not calling load() until they have given it.

Content Security Policy

Two directives, one host:

script-src https://widget.chatharmony.ai;
frame-src  https://widget.chatharmony.ai;

Nothing else — no connect-src, no img-src, no style-src. Everything the chat does happens inside a cross-origin iframe with its own policy. If your CSP is nonce-based, it works without changes.

TypeScript

Everything is typed, and the types are the source of truth for the widget itself rather than a description written after the fact. window.Harmony is declared globally, so a script-tag installation is typed too.


MIT