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

@notilens/notilens

v0.5.4

Published

NotiLens — send alerts to NotiLens from Node.js scripts, apps, and AI agents

Downloads

168

Readme

NotiLens

Send alerts to NotiLens from Node.js scripts, apps, and AI agents.

Two ways to use it — pick one or both:

  • CLI — for shell scripts, bash pipelines, Claude Code hooks, any terminal workflow
  • SDK — for Node.js/TypeScript projects (import and call directly in code)

Installation

npm install -g @notilens/notilens

Or per-project:

npm install @notilens/notilens

CLI

1. Setup

Get your token and secret from the NotiLens dashboard.

notilens init --name my-app --token YOUR_TOKEN --secret YOUR_SECRET

This saves credentials to ~/.notilens_config.json. All future commands read from there — no need to pass token/secret again.

Multiple sources (each notifies a different topic):

notilens init --name scraper --token TOKEN_A --secret SECRET_A
notilens init --name mailer  --token TOKEN_B --secret SECRET_B

List / remove:

notilens sources
notilens remove-source my-app

2. Notify

The simplest way to send a notification — no task or run context needed:

notilens notify order.placed    "Order #1234"      --name my-app
notilens notify disk.space.full "Only 1GB left"    --name my-app --type warning
notilens notify report.ready    "Report is ready"  --name my-app --download_url https://example.com/report.pdf

3. Commands

--task is a semantic label (e.g. email, report). Each task.start creates an isolated run internally — concurrent executions of the same label never conflict.

Task Lifecycle

notilens queue    --name my-app --task email
notilens start    --name my-app --task email
notilens progress "Fetching data"  --name my-app --task email
notilens loop     "Step 3 of 10"   --name my-app --task email
notilens retry    --name my-app --task email
notilens pause    "Rate limited"   --name my-app --task email
notilens resume   "Resuming"       --name my-app --task email
notilens wait     "Awaiting tool"  --name my-app --task email
notilens stop     --name my-app --task email
notilens complete "All done"       --name my-app --task email
notilens error    "Step 3 failed"  --name my-app --task email
notilens fail     "Unrecoverable"  --name my-app --task email
notilens timeout  "Took too long"  --name my-app --task email
notilens cancel   "User cancelled" --name my-app --task email
notilens terminate "Out of memory" --name my-app --task email

task.start prints the internal run_id to stdout. You can capture it if needed — but for sequential scripts, just use --task LABEL and the SDK handles the rest automatically.

Output Events

notilens output.generate "Report ready"     --name my-app --task email
notilens output.fail     "Model unavailable" --name my-app --task email

Input / Human-in-the-loop

notilens input.required "Please confirm the output" --name my-app --task email
notilens input.approve  "Confirmed"                 --name my-app --task email
notilens input.reject   "Rejected"                  --name my-app --task email

Generic Event

notilens track order.placed "Order #1234" --name my-app --meta amount=99.99
notilens track disk.space.full "Only 1GB remaining" --name my-app --type warning

Metrics

Pass any key=value pairs — numeric values accumulate across calls:

notilens metric tokens=512 cost=0.003 --name my-app --task email
notilens metric records=1500          --name my-app --task email

# Reset one metric
notilens metric.reset tokens --name my-app --task email

# Reset all metrics
notilens metric.reset --name my-app --task email

4. Claude Code Hooks Example

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Task",
      "hooks": [{
        "type": "command",
        "command": "notilens start --name claude --task $TASK_ID"
      }]
    }],
    "PostToolUse": [{
      "matcher": "Task",
      "hooks": [{
        "type": "command",
        "command": "notilens complete \"Task done\" --name claude --task $TASK_ID"
      }]
    }],
    "Stop": [{
      "hooks": [{
        "type": "command",
        "command": "notilens complete \"Session ended\" --name claude --task session"
      }]
    }],
    "Notification": [{
      "hooks": [{
        "type": "command",
        "command": "notilens input.required \"Claude needs input\" --name claude --task session"
      }]
    }]
  }
}

Options

| Flag | Description | |------|-------------| | --name <name> | Name identifying the source (required) | | --task <label> | Task label (e.g. email, report) | | --type | Override type: info success warning urgent | | --meta key=value | Custom metadata (repeatable) | | --image_url <url> | Attach an image | | --open_url <url> | Link to open | | --download_url <url> | Link to download | | --tags "tag1,tag2" | Comma-separated tags | | --is_actionable true\|false | Override actionable flag | | --force_send true\|false | Bypass ML filtering. Default true for notify, fail, timeout, terminate, input.required, output.* |


force_send (CLI)

By default NotiLens routes notifications through ML-based filtering. Use --force_send true to bypass ML and deliver immediately, or --force_send false to force ML routing even on high-signal commands.

| Command | Default | |---------|---------| | notilens notify | true | | notilens fail | true | | notilens timeout | true | | notilens terminate | true | | notilens input.required | true | | notilens output.generate | true | | notilens output.fail | true | | Everything else | false |

# Override to route through ML
notilens fail "Error" --name my-app --task email --force_send false

# Override to bypass ML
notilens progress "Critical step" --name my-app --task email --force_send true

Full CLI Example

notilens init --name summarizer --token MY_TOKEN --secret MY_SECRET

notilens start --name summarizer --task report
notilens metric tokens=1024 cost=0.004 --name summarizer --task report
notilens complete "Summary ready" --name summarizer --task report \
  --meta input_file=report.pdf \
  --open_url https://example.com/summary.pdf

SDK

Use the SDK to send notifications directly from your Node.js or TypeScript code.

1. Setup

import { NotiLens } from '@notilens/notilens';

// Option A — pass credentials directly
const nl = NotiLens.init('my-app', { token: 'YOUR_TOKEN', secret: 'YOUR_SECRET' });

// Option B — read from environment variables
// NOTILENS_TOKEN=xxx NOTILENS_SECRET=yyy
const nl = NotiLens.init('my-app');

// Option C — read from saved CLI config (~/.notilens_config.json)
const nl = NotiLens.init('my-app');

All init options:

const nl = NotiLens.init('my-app', {
  token:    'YOUR_TOKEN',   // required (or env var)
  secret:   'YOUR_SECRET',  // required (or env var)
  stateTtl: 86400,          // optional — orphaned state TTL in seconds (default: 86400 / 24h)
});

2. Notify

The simplest way to send a notification — no task or run context needed:

nl.notify('order.placed', 'Order #1234');
nl.notify('disk.space.full', 'Only 1GB left', { level: 'warning' });
nl.notify('report.ready', 'Your report is ready', {
  downloadUrl: 'https://example.com/report.pdf',
  tags: 'report,weekly',
});

// Also available on a run
run.notify('deploy.done', 'Deployed to production', {
  openUrl: 'https://example.com/deploy/123',
});

3. Task Lifecycle

nl.task(label) creates a Run — an isolated execution context with its own state. Multiple concurrent runs of the same label never conflict.

const run = nl.task('email');   // create a run for the "email" task
run.queue();                     // optional — pre-start signal
run.start();                     // begin the run

run.progress('Fetching records');
run.loop('Processing batch 2');
run.retry();
run.pause('Waiting for rate limit');    // non-terminal warning
run.resume('Resuming work');            // non-terminal info
run.wait('Waiting for tool response');  // non-terminal warning

run.error('Non-fatal error');           // task continues

run.complete('All done');           // terminal — clears state
run.fail('Unrecoverable');          // terminal
run.timeout('Exceeded 30s');        // terminal
run.cancel('User cancelled');       // terminal
run.terminate('OOM');               // terminal
run.stop();

4. Input / Human-in-the-loop

run.inputRequired('Please confirm the output');
run.inputApproved('User confirmed');
run.inputRejected('User rejected');

5. Output Events

run.outputGenerated('Summary ready');
run.outputFailed('Model timeout');

6. Metrics

Track any numeric or string values per run — accumulated automatically and included in every notification.

run.metric('tokens', 350);    // set
run.metric('tokens', 210);    // now 560 (numeric values accumulate)
run.metric('cost', 0.0012);
run.metric('model', 'gpt-4'); // strings are replaced, not accumulated

run.resetMetrics('tokens');   // reset one metric
run.resetMetrics();           // reset all metrics

Automatic Timing

NotiLens automatically tracks task timing. These fields are included in every notification's meta payload when non-zero:

| Field | Description | |-------|-------------| | total_duration_ms | Wall-clock time since start | | queue_ms | Time between queue and start | | pause_ms | Cumulative time spent paused | | wait_ms | Cumulative time spent waiting | | active_ms | Active time (total − pause − wait) |


7. Custom Events

run.track('order.placed', 'Order #1234', { meta: { amount: 99.99 } });
run.track('disk.space.full', 'Only 1GB remaining', { level: 'warning' });
nl.track('user.registered', 'New signup', { meta: { plan: 'pro' } });

force_send (SDK)

By default NotiLens routes notifications through ML-based filtering. Pass forceSend: true (or as a positional param) to bypass ML and deliver immediately.

| Method | Default | |--------|---------| | notify() | true | | fail() | true | | timeout() | true | | terminate() | true | | inputRequired() | true | | outputGenerated() | true | | outputFailed() | true | | Everything else | false |

All methods accept forceSend as an overridable param:

// Override a default-true method to go through ML
run.fail('Error', false);
run.timeout('Slow', false);

// Override a default-false method to bypass ML
run.progress('Critical step', true);
run.complete('Done', true);

// Via options for notify/track
nl.notify('low.priority', 'FYI', { forceSend: false });
run.track('custom.event', 'msg', { forceSend: true });

Full SDK Example

import { NotiLens } from '@notilens/notilens';

const nl  = NotiLens.init('summarizer', { token: 'TOKEN', secret: 'SECRET' });
const run = nl.task('report');
run.start();

try {
  run.progress('Fetching PDF');

  const result = await llm.complete(prompt);
  run.metric('tokens', result.usage.totalTokens);
  run.metric('cost', result.usage.cost);

  run.outputGenerated('Summary ready');
  run.complete('All done');
} catch (err) {
  run.fail((err as Error).message);
}

Requirements

  • Node.js >= 18.0.0

License

MIT — notilens.com