Quickstart

From zero to a rendered widget in six steps. Prerequisite: access - membership of the widget-consumers team and a scoped GitHub token.

1. Install the CLI

The signalos CLI ships privately from GitHub Packages as @signallabsai/cli. Once per machine, store your CLI token (classic PAT, read:packages scope - creation walkthrough: Get access):

shell - any OS, writes your user-level npm config
npm config set //npm.pkg.github.com/:_authToken <your token>

Then, in your app repo, route the scope and install:

shell
echo "@signallabsai:registry=https://npm.pkg.github.com" >> .npmrc
pnpm add -D @signallabsai/cli

Commit the repo's .npmrc (it holds no secrets - the token lives in your user-level npm config). Every npx signalos … command below now resolves to the installed CLI.

2. Set the registry token

Use your registry token (fine-grained PAT, repo: signal-widgets only, permission: Contents read-only - creation walkthrough: Get access) and export it permanently:

shell - any OS
npx signalos token set github_pat_XXXX

One command, cross-platform: it persists the token for your shell (setx on Windows, .zshrc/.bashrc/fish on macOS & Linux), writes it to this project's .env.local (and gitignores it), then verifies it against the registry. Prefer doing it by hand? Any mechanism that sets the SIGNALOS_REGISTRY_TOKEN environment variable works.

3. Configure your app (once)

In a Next.js/React app that already uses Tailwind v4 and has run shadcn init:

shell
npx signalos init

This adds the @signalos registry to components.json, verifies your token against the registry, and creates signalos.lock.json. No CLI? Add the block by hand:

components.json
{
  "registries": {
    "@signalos": {
      "url": "https://raw.githubusercontent.com/SignalLabsAI/signal-widgets/main/r/{name}.json",
      "headers": { "Authorization": "Bearer ${SIGNALOS_REGISTRY_TOKEN}" }
    }
  }
}

4. Pull your first widget

shell
npx signalos add data-table

One command pulls the widget and everything it needs: the design tokens (src/styles/signalos.css), the cn() util, and the primitives it builds on - then installs npm dependencies and records versions in the lockfile.

5. Wire the tokens (once)

In your globals.css, after @import "tailwindcss";:

src/app/globals.css
@import "tailwindcss";
@import "../styles/signalos.css";
@custom-variant dark (&:is(.dark *));

6. Render it

src/app/signals/page.tsx
import { DataTable } from "@/components/widgets/data-table/DataTable"

export default function SignalsPage() {
  return (
    <DataTable
      columns={columns}     // ColumnDef<YourRow>[]
      data={rows}           // your domain data, mapped in the screen
      emptyMessage="No signals yet."
    />
  )
}

The screen maps your domain types into widget props - the widget never imports them. That seam is what keeps every pulled widget portable.

Pulling a full page

Pages work the same way - pull a variant, mount it in a route:

shell + route
npx signalos add login-01

# src/app/login/page.tsx
import { Login01 } from "@/components/blocks/login-01/Login01"

export default function LoginPage() {
  return <Login01 onSubmit={({ email, password }) => signIn(email, password)} />
}

Next