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

@kitlangton/hex

v0.3.1

Published

TypeScript client for the HEX local transcription service

Readme

HEX TypeScript Client

@kitlangton/hex provides local transcription through a native HEX helper, with Promise and optional Effect APIs. The host application owns recording, microphone permission, settings, and what happens to the resulting text. HEX owns model preparation and inference.

The package is ESM-only and requires Node.js >=20.3.0 or a compatible host runtime.

Distribution status: a bundled native helper is not published yet. Embedded consumers must currently supply an explicit native command. The examples below use the HEX executable's service --embedded mode; users do not need to launch the HEX desktop app. This package alone is not yet a turnkey native distribution.

Promise API

Pass a model to create() to get a ready-to-use transcriber:

import * as Hex from "@kitlangton/hex"

const transcriber = await Hex.create({
  command: ["/path/to/hex-service", "service", "--embedded"],
  model: "parakeet_unified_en",
  language: "en",
  onProgress: console.log,
})

try {
  // WAV is an ArrayBuffer or Uint8Array supplied by your recording code.
  const result = await transcriber.transcribe(wav, {
    signal: abortController.signal,
  })
  console.log(result.transcript, result.durationMs)
} finally {
  await transcriber.close()
}

create({ model }) starts the helper and waits for download (if necessary), verification, and model loading. Progress can report downloading, verifying, and loading. Model and language are captured when creation starts; language defaults to en, independently of desktop settings. A failed or cancelled creation closes the helper before rejecting. The optional creation signal covers startup and preparation, not the returned transcriber's entire lifetime.

Keep the transcriber alive across recordings to reuse its loaded model. It exposes the bound model, language, and diagnostic pid; audio requests need only WAV bytes. transcribe() never initiates a download. Inference remains bounded by the native service's admission limits; simultaneous requests can receive a busy error instead of entering an unbounded client queue.

Cancellation and ownership

close() is idempotent. It closes the helper's stdin lease, waits for graceful shutdown, then terminates the exact child if it does not exit within the bounded deadline. Close when the feature or application shuts down, not after every clip. No model files are deleted.

Cancelling an in-flight transcribe() closes this transcriber's helper before rejecting. This is intentional: aborting HTTP alone cannot interrupt native inference. All other work on that transcriber also ends; create a new transcriber before the next recording. An already-aborted request is rejected without closing an otherwise usable transcriber. Ordinary request errors do not close it.

This distinction lets hosts that retain recording resources until cancellation settles know that native inference has stopped. Cleanup failures remain visible; they are not reported as successful cancellation. The host owns temporary audio cleanup and any operation deadline, for example through AbortSignal.timeout().

Effect API

Effect is an optional peer. Use Effect >=4.0.0-rc.112 <5.0.0 for this entrypoint; older v4 betas are not supported. Promise-only consumers do not need Effect and can adapt the Promise API to their own framework/runtime version.

import { Effect } from "effect"
import * as Hex from "@kitlangton/hex/effect"

const program = Effect.scoped(Effect.gen(function* () {
  const transcriber = yield* Hex.create({
    command: ["/path/to/hex-service", "service", "--embedded"],
    model: "parakeet_unified_en",
    language: "en",
    onProgress: console.log,
  })

  return yield* transcriber.transcribe(wav)
}))

const result = await Effect.runPromise(program)

The scope owns helper cleanup; there is no manual close() on the Effect transcriber. Put that scope around the voice feature's lifetime when reusing the model. Model preparation is interruptible after bounded helper startup, and interrupting active transcription waits for helper cleanup even when the owning scope remains open. Errors are schema-backed tagged failures; a scope finalizer that cannot close the helper fails as a defect rather than silently leaking it. The progress callback is synchronous, as in the Promise API.

Low-Level Client

Existing create() calls without a model still return { pid, client, close } in the Promise API and a scoped { pid, client } in the Effect API. This is useful for catalog browsing and explicit model management:

const host = await Hex.create({
  command: ["/path/to/hex-service", "service", "--embedded"],
})
try {
  const models = await host.client.models.list()
  await host.client.models.prepare("parakeet_unified_en", {
    language: "en",
    onProgress: console.log,
  })
  const result = await host.client.transcribe({
    audio: { data: wav, contentType: "audio/wav" },
    model: "parakeet_unified_en",
    language: "en",
  })
} finally {
  await host.close()
}

The low-level client retains request-only cancellation; callers own helper shutdown if native work must stop. Effect's low-level client.models.prepare() remains a progress Stream. Hex.layer(options) still supplies the low-level Hex.Service and owns its helper for the layer scope.

Use models.list() to discover available models rather than treating the exported MODEL_IDS as an availability list. apple_speech remains in the public type but is currently unavailable in the native catalog. The optional language argument to models.list() affects readiness metadata, not language filtering; inspect each model's languages and supportsLanguageDetection fields.

Connect to HEX Desktop

Alternatively, reuse the microphone and engine owned by the running HEX desktop app through the Promise entrypoint:

import { connect } from "@kitlangton/hex"

const hex = await connect()
const recording = await hex.dictation.start({ source: "my-app" })

void (async () => {
  for await (const { rmsDb, peakDb } of recording.levels) {
    console.log({ rmsDb, peakDb })
  }
})()

const { transcript, durationMs } = await recording.finish()

Call recording.cancel() to discard. Subscribing to recording.audio enables a bounded best-effort mono Float32 PCM tap at recording.sampleRate. Desktop capture returns raw text and never pastes. capabilities().serviceCapture distinguishes desktop capture from the transcription-only helper. The recording handle renews a ten-second lease with a heartbeat every three seconds; capture expires when renewal stops. Dropping the handle does not stop the heartbeat: always finish or cancel explicitly. Its unguessable ownerToken scopes capture operations and observations automatically.

Host Boundary

  • Use the SDK in Node or Electron main code, not an untrusted renderer. Expose only narrow, validated recording/model operations across the renderer bridge.
  • The direct-child helper does not capture microphone audio. Permissions, NSMicrophoneUsageDescription, the recording indicator, and recording consent belong to the host application. Desktop connect() instead uses HEX's capture.
  • Encode PCM WAV in the host. Do not send compressed recordings such as M4A or WebM and label them as WAV. The service accepts 8-192 kHz audio with 1-8 channels, subject to a 64 MiB upload limit and separate source-frame and normalized-audio budgets. See the service spec for the exact limits; there is no promise of unlimited clip length.
  • Persist the host's model selection in the host, not in HEX desktop preferences.
  • Spawn the signed helper directly. Do not use LaunchServices, open, or NSWorkspace. Validate native packaging/signing in the final consuming app.
  • Never expose the startup bearer token to renderer or web content. Credentials are exchanged over the child pipe and used for authenticated loopback requests.