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

@hasna/shortlinks

v0.5.0

Published

Open-source shortlink manager with local SQLite or hosted /v1 storage, custom-domain provisioning through the Domains API, click tracking, CLI, MCP, redirect server, and SDK

Readme

@hasna/shortlinks

Shortlink management for custom domains — CLI, MCP server, REST API, and a generated SDK.

shortlinks creates Bitly-style short URLs, supports multiple domains, records click analytics, and runs a redirect server. Custom-domain availability and purchase intent go through @hasna/domains; registrar, DNS, nameserver, certificate, and router-binding implementation stays exclusively in the Domains service. The client resolves one store through the @hasna/contracts 1.0.2 client resolver: the hosted /v1 API when a shortlinks credential resolves (the macOS Keychain, ~/.hasna/shortlinks/config/credentials, or HASNA_SHORTLINKS_API_KEY — the URL defaults to the fleet gateway https://api.hasna.com/shortlinks and never needs configuring), or the on-box SQLite database (~/.hasna/shortlinks/shortlinks.db) when the local backend is explicitly opted into with HASNA_SHORTLINKS_LOCAL=1 (alias SHORTLINKS_LOCAL=1), which is announced on stderr as shortlinks: LOCAL mode — …. That environment opt-in is the only door into local storage: --db <path> chooses the database file for such a run and, on its own, is refused. With neither, store-backed commands fail closed — the CLI never falls back to local storage on its own. The shortlinks-serve service reads/writes an app-owned PostgreSQL database when HASNA_SHORTLINKS_DATABASE_URL is configured.

Surfaces

Four surfaces share one core library:

| Surface | Bin / package | Purpose | | --- | --- | --- | | CLI | shortlinks | Interactive/scriptable link + domain management (--json for agents). | | MCP | shortlinks-mcp | Model Context Protocol server (stdio or --http; --version / --help answer without starting a transport) exposing link/domain tools to agents. Fails closed at startup — exits non-zero naming the credential chain — when no credential resolves and the local backend was not opted into. | | REST API | shortlinks-serve | HTTP service: GET /health, /ready, /version, /openapi.json, and a versioned /v1 CRUD API guarded by API-key auth (--version / --help answer without touching the database). | | SDK | @hasna/shortlinks/sdk | Typed fetch client generated from the serve OpenAPI (bun run sdk:generate) plus the resolver-backed createShortlinksApiClient. One package, one ./sdk export subpath — there is no separate -sdk package. |

Hosted service

shortlinks-serve reads/writes PostgreSQL directly via the vendored @hasna/contracts storage kit — no sync engine or cache in the service. A configured HASNA_SHORTLINKS_DATABASE_URL selects the postgresql server data backend; the pool factory fails closed without it. API-key auth comes from @hasna/contracts/auth; mint keys with contracts issue-key --app shortlinks --scopes 'shortlinks:read,shortlinks:write'.

HASNA_SHORTLINKS_DATABASE_URL=$DATABASE_URL \
HASNA_SHORTLINKS_API_SIGNING_KEY=... \
HASNA_DOMAINS_API_KEY=... \
HASNA_DOMAINS_API_URL=https://domains.example.net \
shortlinks-serve            # migrate (idempotent) then serve on :8080
shortlinks-serve migrate    # one-shot migration task

Clients resolve the same hosted API through the @hasna/contracts client resolver — Keychain, ~/.hasna/shortlinks/config/credentials, or HASNA_SHORTLINKS_API_KEY (legacy aliases SHORTLINKS_API_URL / SHORTLINKS_API_KEY; never a DSN). The URL defaults to the fleet gateway.

npm License

Clients resolve their authority and credential through the @hasna/contracts client resolver (never a DSN, never a hand-rolled env read).

Install

bun install -g @hasna/shortlinks

The on-box SQLite database (used only under the explicit local opt-in, see Storage selection) lives at:

~/.hasna/shortlinks/shortlinks.db

Client environment

The CLI, MCP server, and @hasna/shortlinks/sdk all resolve the same way, through the @hasna/contracts 1.0.2 client chain, resolved fresh on every request:

| Variable | Purpose | | --- | --- | | HASNA_SHORTLINKS_API_KEY | Hosted API key. The credential chain also accepts the Keychain item hasna.credentials.shortlinks.api-key (macOS, account HASNA_STATION → hostname → $USER) and ~/.hasna/shortlinks/config/credentials (0600, HASNA_SHORTLINKS_API_KEY=...). Prefixed env wins over disk; the Keychain is re-read per call. | | HASNA_SHORTLINKS_API_URL | Hosted API base URL. Optional: the authority defaults to the fleet gateway https://api.hasna.com/shortlinks (the Keychain api-url item and the credentials file's HASNA_SHORTLINKS_API_URL are also honoured). A declared-but-blank or disagreeing variable fails loudly. | | SHORTLINKS_API_URL / SHORTLINKS_API_KEY | Legacy aliases, accepted by the resolver for one release. | | HASNA_SHORTLINKS_API_KEY_OVERRIDE | Deliberate per-run key that outranks everything else (never populated automatically). | | HASNA_SHORTLINKS_API_KEY_REF | Deliberate pointer to a @hasna/secrets vault item key (resolved at request time). | | HASNA_PROFILE | Selects which identity profile's credential file to use. | | HASNA_SHORTLINKS_LOCAL=1 | The ONLY opt-in to the on-box SQLite store (alias SHORTLINKS_LOCAL=1), announced once per process on stderr (shortlinks: LOCAL mode — …). --db <path> only names the database file for a run that already opted in; without the opt-in it is refused. | | SHORTLINKS_HOME / HASNA_HOME | Relocate the app home (~/.hasna/shortlinks: config, click salt, machine id, and the on-box database). SHORTLINKS_HOME names the directory itself; HASNA_HOME replaces ~/.hasna exactly as it does for the credential chain. Every surface derives the home from the environment it was handed, and a hosted-mode read creates nothing there. | | HASNA_SHORTLINKS_DATABASE_URL | Server only (shortlinks-serve): required Postgres DSN for the service's own pool. Missing configuration fails closed before listening; the hosted server never falls back to SQLite. Never read by clients. |

With no credential resolvable and no local opt-in, store-backed commands exit non-zero with an error naming the chain — never a silent fallback.

| SDK | Purpose | | --- | --- | | createShortlinksApiClient(options) | Build the typed /v1 client with the resolver behind it; the credential is refreshed on every request. | | resolveShortlinksSdkTransport(options) | Read WHERE the authority and credential came from without building a client. An explicit baseUrl with no apiKey pins the authority: the ambient fleet key is never attached to a caller-chosen URL. |

Quick Start

Point the CLI at the hosted shortlinks API, or opt into the on-box store:

# Hosted API (requires a fleet API key — the URL defaults to the gateway):
export HASNA_SHORTLINKS_API_KEY=hsk_...

# Or the explicit local backend:
export SHORTLINKS_LOCAL=1

shortlinks init --domain sho.rt
shortlinks create https://example.com --slug docs
shortlinks serve --host 127.0.0.1 --port 8787

With neither a resolvable credential nor SHORTLINKS_LOCAL=1, store-backed commands exit non-zero with an error naming the required configuration — they never silently serve local data. --db <path> is not an opt-in: passing it alone refuses with one line naming HASNA_SHORTLINKS_LOCAL=1.

Then a request for https://sho.rt/docs redirects to https://example.com and records a click.

Agent-Friendly JSON

Every operational command supports --json:

shortlinks --json create https://example.com --domain sho.rt
shortlinks --json link list
shortlinks --json stats docs --domain sho.rt
shortlinks --json doctor

Errors are emitted as:

{ "error": "message" }

Compact Defaults and Details

Human output is compact by default so agent terminals do not fill with full records. List and status commands show essential fields, truncate long URLs or text, cap human rows, and print the next command to use for details.

Use these gradual disclosure paths when you need more:

shortlinks link list --limit 50
shortlinks link get home --verbose
shortlinks stats home --verbose
shortlinks doctor --verbose
shortlinks domain setup example.test --max-price 5 --auto-renew false --dry-run
shortlinks events list --limit 50
shortlinks webhooks list --limit 50
shortlinks --json link get home

--json remains the machine-readable path and keeps full objects where commands already returned them. Collection commands return at most 20 rows by default; pass an explicit positive --limit when a larger page is required. Prefer --json for automation and --verbose for human debugging.

Example compact output:

https://sho.rt/home -> https://example.com/landing-page-with-a-very-long-path... active
Showing 1 link(s).
Use `shortlinks link get <slug>` for details.

Before this behavior, detail/status commands such as shortlinks link get home, shortlinks stats home, and shortlinks doctor printed full JSON-like objects by default.

CLI

shortlinks init --domain sho.rt
shortlinks domain setup go.example.com --max-price 5 --auto-renew false --years 1 --dry-run
shortlinks domain setup go.example.com --max-price 5 --auto-renew false --years 1 --wait

shortlinks create https://example.com --slug home
shortlinks link create https://example.com/docs --domain sho.rt --title Docs
shortlinks link list
shortlinks link get home --domain sho.rt
shortlinks link disable home --domain sho.rt
shortlinks link enable home --domain sho.rt
shortlinks stats home --domain sho.rt

shortlinks serve --port 8787
shortlinks doctor

Local Domain Setup

Record a local mapping with the machines CLI and print the remaining hosts/proxy setup:

shortlinks local setup sho.rt --port 8787
shortlinks local plan sho.rt --port 8787

The command emits the /etc/hosts line, a Caddy reverse-proxy snippet, and certificate paths. Writing /etc/hosts still requires sudo on macOS.

Custom Domains through the Domains API

Shortlinks never talks directly to a registrar, DNS provider, or Cloudflare. It accepts only business intent and delegates it across the API boundary:

client -> Shortlinks /v1 -> Domains /v1 -> registrar + DNS + delegation + router binding

Every purchase requires an explicit total price cap, renewal decision, and stable idempotency key. The CLI derives shortlinks-domain:<hostname> unless a key is supplied:

shortlinks domain setup go.example.com \
  --max-price 5 \
  --auto-renew false \
  --years 1 \
  --wait

--dry-run prints only the business request and performs no write. Shortlinks rejects registrar, DNS-provider, Cloudflare account/zone, Worker, origin, and provider-state fields. A pending domain is non-routable and cannot become the default; reconciliation activates it only after Domains reports ready.

sho.rt is an example operator-configured default. No hostname is built into the package. Creating it locally does not trigger a purchase. Managed custom-domain projections cannot be locally deleted until a Domains decommission workflow exists, preventing an orphaned edge binding.

Self-hosted infrastructure

The API boundary is not a Hasna-cloud lock-in. Run domains-serve and shortlinks-serve in your own infrastructure, keep registrar and DNS-provider credentials only on Domains, and configure Shortlinks with:

export HASNA_DOMAINS_API_KEY=... # domains:read + domains:purchase
export HASNA_DOMAINS_API_URL=https://domains.example.net

HASNA_DOMAINS_API_URL accepts an HTTP(S) service root with or without /v1. When unset, it defaults to https://api.hasna.com/domains, and the Domains SDK appends /v1 exactly once.

Storage selection

The client resolves ONE Store through the @hasna/contracts client resolver — there is no DSN on any client:

  • hosted /v1 HTTP API (when a credential resolves): the @hasna/contracts chain supplies the key — the Keychain item hasna.credentials.shortlinks.api-key, ~/.hasna/shortlinks/config/credentials (0400/0600), or HASNA_SHORTLINKS_API_KEY (legacy alias SHORTLINKS_API_KEY) — and the authority follows HASNA_SHORTLINKS_API_URL (alias SHORTLINKS_API_URL) or defaults to the fleet gateway https://api.hasna.com/shortlinks, so URLs never need configuring. A URL without a credential (or a declared-but-blank or disagreeing variable) fails loudly — never silent local drift.
  • on-box SQLite (explicit opt-in only): the local database at ~/.hasna/shortlinks/shortlinks.db is used ONLY when the local backend is explicitly selected with HASNA_SHORTLINKS_LOCAL=1 / SHORTLINKS_LOCAL=1 — and selecting it prints shortlinks: LOCAL mode — … on stderr, once per process. --db <path> (and SHORTLINKS_DB) only choose WHICH file that opted-in run uses; --db alone is refused with a line naming the opt-in. The sqlite engine is loaded through a single gated dynamic import, so it is absent from the shortlinks, shortlinks-mcp, and ./sdk entry artifacts.
  • fail closed (no configuration): with no resolvable credential and no local opt-in, store-backed commands exit non-zero with an error naming the chain and the local opt-in — the CLI never silently serves local data and never creates ~/.hasna/shortlinks/shortlinks.db on its own.
# Route the client to the hosted API (bearer key, never a DSN; URL optional):
export HASNA_SHORTLINKS_API_KEY=hsk_...
shortlinks doctor

# Explicit local backend (on-box SQLite — prints "LOCAL mode" on stderr):
export SHORTLINKS_LOCAL=1
shortlinks init --domain sho.rt

The hosted server (shortlinks-serve) is the only component that holds a Postgres connection. It requires HASNA_SHORTLINKS_DATABASE_URL, opens its pool server-side through the sanctioned storage kit, and fails closed before listening when the DSN is absent — the raw RDS DSN is never distributed to clients and the hosted server never falls back to SQLite.

The exported redirect helpers (createShortlinksHandler and serveShortlinks) also refuse implicit storage. Hosted callers resolve the canonical app-base credential authority and inject that store; an omitted store is accepted only when HASNA_SHORTLINKS_LOCAL=1 (alias SHORTLINKS_LOCAL=1) explicitly authorizes the on-box database. dbPath and SHORTLINKS_DB only choose the file after that opt-in.

Development

bun install
bun test
bun run typecheck
bun run build

Repository

The OSS repository is expected to be:

hasna/shortlinks

The local workspace folder is named shortlinks; the published package and GitHub repo use bare names without the retired open- prefix.

License

Apache-2.0. See LICENSE.