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

@setu-dental/cli

v1.3.0

Published

Setu CLI — build, review and publish claimable practice and dentist profiles.

Readme

Setu CLI (internal)

Build claimable practice and dentist profiles from the command line.

Install

cd packages/cli && npm link      # gives you the `setu` command
setu login                       # paste the internal CLI key

The key is the SETU_CLI_API_KEY secret from the Setu backend. It is stored at ~/.config/setu/config.json (chmod 600). You can also set SETU_CLI_API_KEY and SETU_API_URL as environment variables in CI.

Build one profile

setu practice create --url https://smiledental.co.uk
setu practice create --name "Smile Dental" --town Portsmouth
setu practice review <draft-id>
setu practice publish <draft-id>

create scrapes the practice website (services, pricing, hours, team, images), pulls their Google listing (address, phone, rating, reviews, opening hours) and returns a readiness score. Nothing is public until publish.

publish creates an unclaimed listing: it appears in patient search with an "Unclaimed" badge, ranked below live subscribed practices, with enquiry capture instead of booking. It also seeds any clinicians the AI detected (skip with --no-dentists).

Bulk import

CSV with any of these columns: name, website, town, postcode, email, phone.

setu practice import practices.csv --publish

Rows are processed one at a time (Firecrawl / Google rate limits) and failures are listed at the end so you can retry them.

Claim links

setu practice claim-link <practice-id>
setu status                       # every seeded profile and its claim state
setu status <practice-id>         # one profile plus its claim attempts
setu status <profile-id> --full   # every field, photos, prices, services, clinicians
setu status --dentist             # list clinicians instead of practices

Send the claim link in outreach. The practice signs in, and is verified either by their email domain matching their website, or by a code sent to the public contact address on their Google listing. On success the profile transfers to them, pre-filled, and they continue through the normal onboarding and subscription flow.

Reviewing claims

setu claims                        # pending claims + unclaimed profiles by readiness
setu claims --ready                # only profiles ready to promote
setu claims --status all           # every claim, any status
setu claims show <claim-id>        # claimant, method, attempts, target profile
setu claims approve <claim-id>     # transfer the profile to the claimant
setu claims decline <claim-id> --reason "Not the practice owner"

Approving a practice claim makes the claimant the owner, adds an owner membership and marks the practice's clinicians as claimed. A claim can only be approved once the claimant has signed in, so there's an account to transfer to.

Clinicians

setu dentist create --name "Dr Jane Roe" --practice <id> --profile-url https://…/team/jane \
  --photo https://…/jane.jpg --gallery "https://…/a.jpg,https://…/b.jpg" \
  --phone 02392000000 --website https://… --booking https://…/book

setu practice list --limit 100 --search "smile"
setu dentist  list --practice <practice-id>
setu dentist  status <dentist-id> --full

Flags

--json for machine-readable output, --operator "Your Name" to attribute seeded profiles to you.

Reliability

  • Every request has a timeout (SETU_TIMEOUT_MS, default 240s) and retries transient failures (network drops, 429, 5xx) with exponential backoff (SETU_RETRIES, default 3). Retry-After is honoured.
  • --verbose prints retry attempts and stack traces.
  • setu logout removes the stored key from this machine.
  • Unknown commands and failed bulk imports exit non-zero (safe for scripts/CI).

Bulk import

setu practice import practices.csv --dry-run            # validate only
setu practice import practices.csv --publish \
  --concurrency 3 --out results.json                    # run
setu practice import practices.csv --resume results.json # retry failures only

Rows need either a website/url, or a name plus town/city; anything else is reported and skipped before any API call is made.

Editing seeded profiles

setu practice update <practice-id> --fields                     # what you can set
setu practice update <practice-id> --set tagline="Gentle care" --set emergency_available=true
setu dentist  update <dentist-id>  --set bio="20 years in implants"

# Booking / website link shortcuts
setu practice update <practice-id> --booking https://clinic.co.uk/book
setu dentist  update <dentist-id>  --booking https://clinic.co.uk/book --website https://clinic.co.uk

Lists take comma-separated values, e.g. --set services="Implants,Whitening".

Everything on the practice profile settings screen is settable. The few values that live in the nested profile record — positioning, amenities, default currency — use --set-enrichment:

setu practice update <practice-id> --set-enrichment positioning.summary="Calm, modern care in Leeds"
setu practice update <practice-id> --set-enrichment amenities="Wi-Fi,Free parking,Step-free access"
setu practice update <practice-id> --set-enrichment positioning.archetype=boutique
setu practice update <practice-id> --set parking_info="Two-hour free bay on site" \
  --set accessibility="Step-free,Hearing loop" --set languages="English,Polish"

Photos and logos

setu practice images <practice-id> --list
setu practice images <practice-id> --add https://.../photo.jpg
setu practice images <practice-id> --remove 2                   # by index, or pass the URL
setu practice images <practice-id> --order "https://.../hero.jpg"   # move these to the front
setu practice images <practice-id> --replace "https://a.jpg,https://b.jpg"
setu practice images <practice-id> --logo https://.../logo.png
setu dentist  images <dentist-id>  --logo https://.../headshot.jpg  # main photo

Upload from your machine (or copy a remote image into Setu storage) instead of linking to someone else's server:

setu practice images <practice-id> --upload ./reception.jpg
setu practice images <practice-id> --upload-logo ./logo.png
setu dentist  images <dentist-id>  --upload-logo https://.../headshot.jpg

Prices and services

setu practice pricing <practice-id> --list
setu practice pricing <practice-id> --set "Dental implant=1200" --set "Whitening=300-450"
setu practice pricing <practice-id> --remove "Whitening"
setu dentist  pricing <dentist-id>  --set "Hygiene visit=75"
setu practice update <practice-id> --set services="Implants,Whitening,Invisalign"

Practice prices update the practice's treatment list too. Dentist prices are saved as individual treatment rows shown on the clinician's profile.

Removing seeded profiles

setu practice delete <practice-id> --unpublish   # hide from patients
setu practice delete <practice-id>               # delete (unclaimed only)
setu dentist  delete <dentist-id> --unpublish
setu dentist  delete <dentist-id>

Claimed or user-owned profiles are always refused.

Completing existing profiles (admin keys)

A key created with "Let this key complete claimed listings" ticked carries the admin:profiles scope. It can read and improve any practice or clinician — claimed, self-serve or seeded. It can never delete a record, transfer ownership, resolve a claim, verify a profile or change whether it is live.

setu permissions                       # what this key may do
setu search "Islington Smiles" --website https://www.islingtonsmiles.co.uk/ --postcode "N1 1RU"
setu status --all                      # list every profile, not just seeded ones

# Preview first — nothing is written
setu practice enhance <id> \
  --set description="…" --set booking_url=https://… \
  --price "Root canal treatment=from £295" \
  --logo https://site/logo.png --photo https://site/1.jpg --photo https://site/2.jpg \
  --team team.json --source description=https://site/about --researched 2026-09-09

setu practice enhance <id> … --apply   # save
setu practice enhance <id> --history   # audit trail
setu practice enhance <id> --rollback <run-id>

Rules the server enforces on every run:

  • Records are never deleted, recreated, unclaimed, transferred or unpublished.
  • Empty fields are filled; a populated field is only changed when you pass --replace <field>.
  • A blank value never overwrites existing content.
  • Photos are imported into Setu storage, deduplicated and added to the existing gallery.
  • Prices keep their "from" qualifiers, ranges and conditions; conflicting sources are flagged instead of guessed, and expired offers are excluded.
  • Clinicians are matched first (GDC number, then name + practice). Nothing is created unless you pass --create-dentists, and existing practice associations are only added to, never removed. Hygienists, therapists and orthodontic therapists keep their real role.
  • Runs are idempotent, audited, and can be rolled back. --expect-updated <timestamp> refuses the write if the owner has edited the record in the meantime.

team.json example:

[{ "name": "Dr Marisa Monteiro", "role": "principal", "credentials": "Principal and Invisalign Dentist",
   "gdc_number": "123456", "photo_url": "https://…", "source_url": "https://…/meet-the-team/" }]