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

scentience

v2.0.0

Published

Connect to Scentience olfaction instruments via BLE Bluetooth

Readme

scentience

npm-installable package for interacting with Scentience olfaction instruments via BLE Bluetooth.

Installation

npm install scentience

For Node.js environments, also install the optional BLE polyfill:

npm install webbluetooth

Browser and React environments use the native Web Bluetooth API — no extra dependencies needed.

Quick Start

import { ScentienceDevice } from 'scentience';

const device = new ScentienceDevice('<YOUR_API_KEY>');

await device.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' });

// Single reading
const sample = await device.sampleBLE({ async: true });
console.log(sample);

// Continuous stream
device.on('data', (data) => console.log(data));
device.on('error', (err) => console.error(err));
await device.streamBLE({ async: true });

React Example

import { useEffect, useState } from 'react';
import { ScentienceDevice } from 'scentience';

export default function App() {
  const [reading, setReading] = useState(null);

  useEffect(() => {
    const device = new ScentienceDevice('<YOUR_API_KEY>');

    device
      .connectBLE({ charUuid: '<YOUR_CHAR_UUID>' })
      .then(() => {
        device.on('data', setReading);
        return device.streamBLE({ async: true });
      })
      .catch(console.error);

    return () => device.disconnect();
  }, []);

  return <pre>{JSON.stringify(reading, null, 2)}</pre>;
}

API

new ScentienceDevice(apiKey)

| Parameter | Type | Description | |-----------|----------|-------------------------| | apiKey | string | Your Scentience API key |


device.connectBLE(options)

Scan for and connect to a Scentience peripheral.

| Option | Type | Default | Description | |-------------|----------|--------------------------------------------|--------------------------------------| | charUuid | string | '0000abcd-0000-1000-8000-00805f9b34fb' | GATT characteristic UUID | | deviceUid | string | null | Filter scan to a specific device UID |

Returns Promise<ScentienceDevice>.


device.sampleBLE(options)

Request a single sensor reading.

| Option | Type | Default | Description | |---------|-----------|---------|-----------------------------------------------| | async | boolean | false | Return a Promise resolving with the data payload |

Returns Promise<ScienceData>.


device.streamBLE(options)

Begin continuous sensor streaming. Use .on('data', cb) to receive readings.

| Option | Type | Default | Description | |---------|-----------|---------|----------------------------------------------------------| | async | boolean | false | Return a Promise that stays pending until stopStream() |

Returns Promise<void>.


device.on(event, callback) / device.off(event, callback)

Subscribe or unsubscribe from stream events.

| Event | Callback signature | |---------|-------------------------| | data | (data: ScienceData) => void | | error | (err: Error) => void |


device.stopStream()

Stop an active stream and release the BLE notification subscription.


device.disconnect()

Stop the stream and close the GATT connection.


device.isConnected

booleantrue when a GATT connection is active.


Data Payload

Sensor readings are delivered as a JSON object. Chemical compound fields are only present when their detected magnitude is greater than zero.

{
  "UID": "device_identifier",
  "TIMESTAMP": "2026-03-23T12:00:00.000Z",
  "ENV_temperatureC": 22.4,
  "ENV_humidity": 45.1,
  "ENV_pressureHpa": 1013.2,
  "STATUS_opuA": 120,
  "BATT_health": 98,
  "BATT_v": 3.85,
  "BATT_charge": 87,
  "BATT_time": 14.2,
  "CO2": 412,
  "VOC": 0.03
}

| Prefix | Description | |----------|---------------------------| | ENV_ | Environmental parameters | | BATT_ | Battery metrics | | (none) | Chemical compound readings |

Connect to a Specific Device

await device.connectBLE({
  charUuid: '<YOUR_CHAR_UUID>',
  deviceUid: '<DEVICE_UID>',
});

Olfaction-Vision-Language Embeddings

ScentienceEmbedder provides access to the four COLIP OVL models — a joint multimodal embedding space for olfaction, vision, and language data.

Install the required peer dependency:

npm install @huggingface/inference

| Variant | Description | |-------------------|--------------------------------------------------------| | colip-small-base| Fast inference; edge robotics, mobile | | colip-small-gat | Graph-Attention variant; edge with higher accuracy | | colip-large-base| Best accuracy for online/server tasks | | colip-large-gat | Graph-Attention variant; highest accuracy |

Example 1 — Embed a live sensor reading and match against text labels

import { ScentienceDevice, ScentienceEmbedder } from 'scentience';

const device = new ScentienceDevice('<SCENTIENCE_API_KEY>');
const embedder = new ScentienceEmbedder({
  model: 'colip-large-base',
  apiToken: '<HF_TOKEN>',
});

await device.connectBLE({ charUuid: '<CHAR_UUID>' });
const reading = await device.sampleBLE({ async: true });

// Embed the sensor reading and a set of candidate text labels
const [scentVec, ...labelVecs] = await Promise.all([
  embedder.embedOlfaction(reading),
  embedder.embedText('fresh coffee'),
  embedder.embedText('pine forest'),
  embedder.embedText('ocean breeze'),
]);

const ranked = ScentienceEmbedder.rankBySimilarity(scentVec, labelVecs);
const labels = ['fresh coffee', 'pine forest', 'ocean breeze'];
console.log('Best match:', labels[ranked[0].index], `(score: ${ranked[0].score.toFixed(3)})`);

Example 2 — Load the small-GAT model and embed text + image

import { ScentienceEmbedder } from 'scentience';

const embedder = new ScentienceEmbedder({
  model: 'colip-small-gat',
  apiToken: '<HF_TOKEN>',
});

const textVec  = await embedder.embedText('smoky oak barrel');
const imageVec = await embedder.embedImage('https://example.com/barrel.jpg');

const similarity = ScentienceEmbedder.cosineSimilarity(textVec, imageVec);
console.log('Text–image similarity:', similarity.toFixed(4));

ScentienceEmbedder API

| Method / Property | Description | |-------------------------------------------------|--------------------------------------------------------------------| | new ScentienceEmbedder({ model, apiToken, modelId? }) | Create a client for the given model variant. | | embedder.embedText(text) | Embed a text string. Returns Promise<number[]>. | | embedder.embedImage(blobOrUrl) | Embed an image (Blob, ArrayBuffer, or URL). Returns Promise<number[]>. | | embedder.embedOlfaction(sensorData) | Embed a ScienceData sensor reading. Returns Promise<number[]>. | | embedder.modelId | The resolved HF repo ID being used. | | ScentienceEmbedder.cosineSimilarity(a, b) | Cosine similarity between two vectors. Returns number in [-1, 1].| | ScentienceEmbedder.rankBySimilarity(q, vecs) | Rank candidates by similarity to query. Returns sorted array. |

Note on model IDs: The COLIP_MODELS map assumes repos at kordelfrance/colip-{size}-{variant}. If the models are published under different IDs, pass modelId directly:

new ScentienceEmbedder({ apiToken: '<HF_TOKEN>', modelId: 'kordelfrance/my-custom-id' })

Logging & Export

ScentienceLogger collects readings and exports them to JSON or CSV.

Stream to CSV (Node.js)

import { ScentienceDevice, ScentienceLogger } from 'scentience';

const device = new ScentienceDevice('<YOUR_API_KEY>');
const logger = new ScentienceLogger();

await device.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' });

device.on('data', (d) => logger.log(d));
await device.streamBLE({ async: true });

// Later — write readings.csv to disk
await logger.exportCSV('readings.csv');

Stream to CSV (React / browser download)

import { useEffect, useRef } from 'react';
import { ScentienceDevice, ScentienceLogger } from 'scentience';

export default function App() {
  const loggerRef = useRef(new ScentienceLogger());
  const deviceRef = useRef(null);

  useEffect(() => {
    const device = new ScentienceDevice('<YOUR_API_KEY>');
    deviceRef.current = device;

    device
      .connectBLE({ charUuid: '<YOUR_CHAR_UUID>' })
      .then(() => {
        device.on('data', (d) => loggerRef.current.log(d));
        return device.streamBLE({ async: true });
      })
      .catch(console.error);

    return () => device.disconnect();
  }, []);

  const downloadCSV = () => loggerRef.current.exportCSV('readings.csv');
  const downloadJSON = () => loggerRef.current.exportJSON('readings.json');

  return (
    <>
      <button onClick={downloadCSV}>Download CSV</button>
      <button onClick={downloadJSON}>Download JSON</button>
    </>
  );
}

ScentienceLogger API

| Method / Property | Description | |---------------------------------|------------------------------------------------------| | logger.log(data) | Add a reading to the log. Returns this. | | logger.data | Array of all logged records. | | logger.size | Number of records in the log. | | logger.clear() | Remove all records. Returns this. | | logger.toJSON(indent?) | Serialize records to a JSON string. | | logger.toCSV() | Serialize records to a CSV string. | | logger.exportJSON(filename?) | Write/download JSON file. Default: scentience-data.json | | logger.exportCSV(filename?) | Write/download CSV file. Default: scentience-data.csv |

CSV column handling: the header row is the union of all keys across every record. Chemical compound columns are included whenever at least one reading detected them — missing values in other rows appear as empty cells.

License

MIT