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

@semilayer/cli

v1.20.0

Published

SemiLayer CLI — init, generate, push, status, dev

Downloads

2,109

Readme


semilayer is the primary interface to SemiLayer. It scaffolds config, pushes schema, indexes data, generates a typed client, streams rows live, and lets you compose searches with the same muscle memory as curl | jq.

One config file. One push. Indexing, access rules, and a typed client — handled.

Install

npm i -g @semilayer/cli           # global
pnpm add -D @semilayer/cli        # per-project

Requires Node 22+.

Zero to search in four commands

semilayer login                   # browser-based OIDC
semilayer init                    # pick org/project/env, scaffold sl.config.ts, mint a key
semilayer push --resume-ingest    # validate + upload config, kick off indexing
semilayer run search articles -q "postmortems about cascading failure"

That's the whole loop. Everything below is how to go further.

Define your data

sl.config.ts is the source of truth. A lens is a semantic view over a table — fields, mapping, operations, access rules.

import { defineConfig } from '@semilayer/core'

export default defineConfig({
  stack: 'my-app',
  sources: {
    main: { bridge: '@semilayer/bridge-postgres' },
  },
  lenses: {
    articles: {
      source: 'main',
      table: 'articles',
      fields: {
        id: { type: 'number', primaryKey: true },
        title: { type: 'text', searchable: true },
        body: { type: 'text', searchable: true },
        author_id: { type: 'number' },
      },
      grants: {
        search: 'public',
        similar: 'public',
      },
    },
  },
})

Push it:

semilayer push --resume-ingest

push validates the config, diffs against remote, refuses to clobber drift (use --force if you mean it), and starts ingest. Every push is content-hashed — idempotent by default.

Run queries from the shell

semilayer run is three subcommands: search (semantic), query (filter + order), similar (nearest-neighbor). They auto-format for TTY and spit NDJSON when piped.

# semantic search
semilayer run search articles -q "how we scaled Postgres" --limit 20

# structured query with filter + ordering
semilayer run query articles --where '{"author_id":42}' --order-by -created_at -l 50

# similar to a known record
semilayer run similar articles --id 1337 --min-score 0.8

Compose like unix

run reads stdin. Pipe one lens into another with --join:

# top 10 semantic hits for "auth bugs" → pull their authors
semilayer run search issues -q "auth bugs" -o json \
  | semilayer run query users --join stdin:author_id=id

Include related lenses

# CSV shorthand
semilayer run search articles -q "cache invalidation" -i "comments(limit:3),author"

# or full JSON when you need where/select/orderBy on the include
semilayer run query articles -i '{"comments":{"where":{"approved":true},"limit":5}}'

Use a scoped API key directly

No .semilayerrc? No problem. Skip the JWT entirely:

semilayer run search articles -q "..." --api-key sk_live_xxx

Keep data fresh

Three shapes of ingest depending on how dangerous you want to be:

semilayer sync articles            # smart: full scan, hash dedup, tombstone deletes
semilayer sync articles --rebuild  # nuke the partition, re-embed everything
semilayer push --rebuild           # same, but for ALL lenses (confirms first)

sync is the workhorse — it catches deletes and only re-embeds rows whose mapped content actually changed. Use it when your source doesn't have reliable updated_at.

Pause and resume are instant (ingest checks between pages and saves its cursor):

semilayer pause articles
semilayer resume articles

Stream live

stream opens a WebSocket and yields rows as they land. observe watches a single record.

# live tail of new "errors" rows
semilayer stream errors --api-key sk_live_xxx

# follow a single order's state
semilayer observe orders ord_9f2 --api-key sk_live_xxx

Both auto-switch to NDJSON when piping — perfect for feeding Sentry, Slack, whatever.

Drive feeds from the shell

The feed command group hits the feed operation — list, view, tail, and explain named feeds without going through Beam codegen.

The lens-feed identifier is <lens>.<feedName> (mirrors how Beam emits them).

# What feeds does this lens have?
semilayer feed list posts --api-key sk_live_xxx

#   Lens:  posts
#   Feeds:  3
#   NAME       RULE    CANDIDATES  PAGE  SCORERS
#   discover   public  embeddings  12    similarity+recency+engagement
#   latest     public  recent      20    recency
#   relatedTo  public  embeddings  6     similarity

# Fetch one page (formatted table or --json)
semilayer feed view posts.discover --api-key sk_live_xxx --limit 5

# Stream tick events — debounced "hey, there's new content" pings
semilayer feed tail posts.discover --api-key sk_live_xxx
#   { "type": "tick", "name": "discover", "newCount": 3, "topChanged": false, ... }
#   { "type": "tick", "name": "discover", "newCount": 1, "topChanged": false, ... }

# Per-record explain — sk-only, returns per-scorer math
semilayer feed explain posts.discover --api-key sk_live_xxx --record 42

#   Record:  42
#   Final score:  0.8614
#   Rank:  3
#
#   Per-scorer contributions:
#   SCORER      WEIGHT  RAW     WEIGHTED
#   similarity  0.55    0.50    0.2750
#   recency     0.15    0.72    0.1080
#   engagement  0.20    1.00    0.2000

Pass context via --context @file.json or inline JSON. Pipe feed tail output into jq for shell-native observability.

Generate a typed client

semilayer generate

Emits a Beam client with every lens, operation, and relation fully typed:

import { beam } from './beam'

const { results } = await beam.articles.search({
  query: 'cache invalidation',
  include: { comments: { limit: 3 } },
})

// results[0].comments is correctly typed — editor knows the shape

Regenerates from remote config. CI should run generate on every build.

Know where you stand

semilayer status              # lens health, queue depth, ingest progress, quota bars
semilayer whoami              # current identity + org/project/env context
semilayer billing status      # plan, usage, next invoice

status shows per-lens state (paused | indexing | ready | error), pending ingest runs, and your quota bars for embeddings, API requests, and rows.

Management — everything the console does

semilayer orgs list | create | switch
semilayer projects list | create | delete
semilayer envs list | create | delete
semilayer keys list | create | revoke          # sk_, pk_, ik_
semilayer sources list | connect | test        # wire a database
semilayer lenses list | show | diff
semilayer members list | invite | role | remove
semilayer auth show | set                      # OIDC access rules
semilayer config get | set                     # per-lens runtime config
semilayer export                               # dump the remote config as TS

Everything the web Console does, the CLI does first. CLI parity is a project invariant — if it ships in the UI, it ships here.

Key types — blast radius, encoded

| Prefix | Role | Scope | Use in | |-------|------|-------|--------| | sk_ | Service key | Full env, bypasses row-level access rules | Backend servers | | pk_ | Public key | Enforces access rules via X-User-Token / userToken | Browsers, mobile | | ik_ | Ingest key | Webhook ingest only — cannot read | Data pipelines, CDC hooks |

The CLI refuses to use ik_ keys for reads or streams. Use the right key and the rest is enforced server-side.

Danger switches (use with intent)

--rebuild                          # drops vectors, re-embeds from scratch
--skip-confirmation-dangerously    # for CI — say what you mean
--force                            # override drift detection on push

If the flag has the word "dangerously" in it, so does what you're doing.

Everywhere you'd want it

  • Pipes in, pipes out. -o json and NDJSON for machine consumption, auto-pretty for humans.
  • Exit codes match semantics: 1 on error, 0 on success, typed close codes on stream termination (4290 concurrency cap, 4291 quota, 4401 auth, 4403 streaming disabled).
  • .semilayerrc is per-directory. Mono-repo friendly.
  • --api-key or --user-token override context when you need to.
  • Everything runs the same whether you're on SaaS (api.semilayer.com) or a self-hosted enterprise service — set serviceUrl once.

Programmatic use

The CLI also exports its lib modules — useful for scripts and test harnesses:

import { loadContext, createApiClient, loadConfig } from '@semilayer/cli'

const ctx = await loadContext()
const api = await createApiClient()
const { config } = await loadConfig()

Docs & support

License

MIT