@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-projectRequires 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-ingestpush 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.8Compose 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=idInclude 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_xxxKeep 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 articlesStream 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_xxxBoth 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.2000Pass context via --context @file.json or inline JSON. Pipe feed tail output into jq for shell-native observability.
Generate a typed client
semilayer generateEmits 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 shapeRegenerates 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 invoicestatus 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 TSEverything 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 pushIf the flag has the word "dangerously" in it, so does what you're doing.
Everywhere you'd want it
- Pipes in, pipes out.
-o jsonand NDJSON for machine consumption, auto-pretty for humans. - Exit codes match semantics:
1on error,0on success, typed close codes on stream termination (4290concurrency cap,4291quota,4401auth,4403streaming disabled). .semilayerrcis per-directory. Mono-repo friendly.--api-keyor--user-tokenoverride context when you need to.- Everything runs the same whether you're on SaaS (
api.semilayer.com) or a self-hosted enterprise service — setserviceUrlonce.
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
- Full reference: semilayer.dev
- Issues: github.com/semilayer/semilayer
- Run
semilayer --helporsemilayer <command> --helpanywhere.
License
MIT
