Norq

CLI Reference

The norq CLI is a single binary that handles all Norq operations: project setup, linting, compilation, testing, preview, sending, code generation, and editor integration.

Installation

Coming soon. Package registry distribution is not yet available. See norq.sh for updates. To build from source: make install (requires Rust toolchain).

Commands

norq init

Create a new Norq project in the current directory.

norq init              # minimal project (config only)
norq init --example    # project with example templates

Creates norq.config.yaml and notifications/ directory with system/, transactional/, and promotional/ type folders. With --example, adds three ready-to-use notifications:

Notification Channels
system/security-alert email, sms, slack, push
transactional/order-confirmation email, sms, slack, push
promotional/weekly-digest email, sms, slack, push

Each notification includes data.schema.yaml, data.samples.yaml, and tests.yaml.

norq new

Scaffold a new notification directory.

norq new transactional/order-shipped
norq new transactional/order-shipped --channels email,sms,slack
norq new promotional/marketing/promotions/welcome-offer --channels email

Creates:

notifications/transactional/order-shipped/
  email.md
  data.schema.yaml
  data.samples.yaml

The --channels flag controls which channel template files are created. Without it, only email.md is scaffolded. With --channels email,sms,slack, the directory also includes sms.md and slack.md.

The first segment must be a notification type (system, transactional, or promotional). Within each type folder, supports category/subcategory paths with / separators (up to 4 segments: type/category/subcategory/name). Each segment must match [a-z0-9]+(-[a-z0-9]+)* (lowercase, hyphen-separated). Intermediate directories are created automatically.

norq lint

Lint notification templates for structure, syntax, and deliverability issues.

norq lint                                # all notifications
norq lint transactional/order-shipped    # one notification
norq lint --json                         # JSON output (for CI/tooling)
Flag Description
--json Output diagnostics as JSON

Output is sorted by max severity per file (Error-bearing files first, then Warning-bearing, then Info-only), then by severity within each file, then by line number. norq lint exits non-zero when any diagnostic has severity Error.

Norq checks 96 rules across structure, template syntax, accessibility, brand health, and channel-specific deliverability:

Category Rules Channels
Structure 9 rules All
Template syntax 15 rules All
Accessibility (WCAG) 9 rules All
Email deliverability 17 rules Email
Brand 9 rules Project-level (one pass per brand.yaml)
SMS 6 rules SMS
Push 10 rules Push
WhatsApp 6 rules WhatsApp
Slack 3 rules Slack
MS Teams 2 rules MS Teams

Brand lint rules (run once per project against brand.yaml):

Code Severity Description
brand/broken-ref error A {path.to.token} alias or basedOn target doesn’t exist.
brand/cyclic-ref error Token alias chain or basedOn chain forms a cycle.
brand/unknown-pipe error Color expression uses an unsupported pipe (only darken, lighten, alpha, mix, contrast in v1).
brand/invalid-pipe-arg error Pipe receives the wrong arg shape (e.g. percentage out of range).
brand/bad-color error Color value is not a parseable sRGB hex.
brand/invalid-dimension error Dimension uses a unit other than px / rem.
brand/missing-primary warn No colors.brand token. Most templates expect a primary brand color.
brand/missing-typography warn An element-typography.<tag> mapping points at a typography token that isn’t defined.
brand/font-not-delivered warn A typography token’s fontFamily references {fonts.X} but no fonts.X block is declared.
brand/orphaned-token warn A custom token is defined but no other brand entry references it (the 17 standard EmailTheme color names and the body/code typography tokens are exempt; templates aren’t scanned).
brand/unknown-element-type warn defaults.<x> targets an element the email compiler doesn’t know about (only image, button, heading, text, divider, section are recognised).

email/low-contrast is also extended to scan brand.styles.* bundles that declare both bg and color – references resolve through the brand, so a style like { bg: "{colors.brand}", color: "{colors.body}" } will fire if the resolved pair fails WCAG AA.

Template attr token rules (run on every directive param + block attr):

Code Severity Description
template/unknown-color-token error A color= value is neither a #hex literal nor a name defined in brand.tokens.colors. Diagnostic message lists every valid option for the current project.
template/unknown-bg-token error Same as above for bg=.
template/nested-columns-unsupported warn A :::columns directive is nested inside a :::col ancestor (including via :::section, which is parser-shorthand for :::columns + :::col). The inner columns silently disappear during compilation; the fix is to place column blocks as siblings with a shared bg.

These rules supersede the older template/invalid-attr-value for bg/color keys – attr values that previously rendered as empty backgrounds now fail the lint with an actionable message.

Push lint rules:

Code Severity Description
push/missing-title error No title source (frontmatter, :::header, or first heading).
push/title-length warn Title exceeds 50 characters.
push/body-length info Body exceeds 120 characters.
push/payload-size warn Payload exceeds 4 KB.
push/too-many-actions warn More than 2 action buttons.
push/unknown-ios-key warn ios: frontmatter has an unknown key.
push/unknown-android-key warn android: frontmatter has an unknown key.
push/unknown-web-key warn web: frontmatter has an unknown key.
push/invalid-priority error android.priority must be normal or high.
push/invalid-interruption-level error ios.interruption-level must be one of passive, active, time-sensitive, critical.

See Lint Rules for the complete reference with descriptions, severity levels, and fix guidance.

norq compile

Compile a notification template into channel payloads.

norq compile transactional/welcome --json
norq compile transactional/welcome --channel email --json
norq compile transactional/welcome --sample "New user" --json
norq compile transactional/welcome --data ./data.json --json
norq compile transactional/welcome --channel email --sample "New user" --json
Flag Description
--channel <c> Compile only this channel
--data <file> Path to a JSON data file
--sample <name> Named sample from data.samples.yaml
--json Output as JSON (default for programmatic use)
--strict Force strict-mode runtime validation on (overrides config)
--no-strict Force strict-mode off (overrides config)

If neither --data nor --sample is provided, the first sample is used automatically.

Strict mode is on by default. It refuses to emit output when the template references data missing from the supplied data object, uses an unknown pipe, or passes a wrong-type pipe argument. Opt out per-invocation with --no-strict, or set strict: false in norq.config.yaml for a project-wide opt-out. See Strict mode below.

norq test

Run assertion tests defined in tests.yaml.

norq test                                # all notifications
norq test transactional/order-shipped    # one notification
norq test --json           # JSON output

See the testing page for test syntax and assertion operators.

norq preview

Preview compiled output for a specific channel.

norq preview transactional/welcome --channel email
norq preview transactional/welcome --channel email --sample "Pro user"
norq preview transactional/welcome --channel email --terminal
Flag Description
--channel <c> Channel to preview (required)
--sample <name> Named sample to use
--terminal Print to terminal instead of opening a browser

norq render

Render email previews and device screenshots via a render-capable provider. Subcommands:

norq render preview

Render an email to a single image (desktop or mobile viewport).

norq render preview transactional/welcome
norq render preview transactional/welcome --device mobile
norq render preview transactional/welcome --dark-mode --out preview.png
norq render preview transactional/welcome --sample "Pro user" --device mobile
Flag Description
--device <viewport> Viewport size: desktop or mobile (default: desktop)
--dark-mode Render in dark mode
--sample <name> Named sample from data.samples.yaml
--data <file> Path to a JSON data file
-o, --out <path> Output file path. Without --out, raw image bytes are written to stdout

norq render screenshot

Render an email as it appears in a specific email client.

norq render screenshot transactional/welcome --client gmail_web
norq render screenshot transactional/welcome --client outlook_2021 --out screenshot.png
norq render screenshot transactional/welcome --client iphone_15 --sample "New user"
Flag Description
--client <name> Email client to render in (required). Examples: outlook_2021, gmail_web, iphone_15, apple_mail
--sample <name> Named sample from data.samples.yaml
--data <file> Path to a JSON data file
-o, --out <path> Output file path. Without --out, raw image bytes are written to stdout

Both render subcommands require a provider with render capabilities. Currently only the suprsend built-in provider supports rendering (preview + screenshot). If no render-capable provider is configured, you’ll see:

Error: Provider 'console' does not support op 'preview'

Configured providers:
  console          → no render

Hint: rendering requires a provider with render capabilities.
  Built-in: set NORQ_SUPRSEND_WORKSPACE_KEY to enable SuprSend (supports preview + screenshot)

norq send

Compile and send a notification via configured providers.

norq send transactional/welcome --to '{"email":"user@example.com"}' --sample "New user"
norq send transactional/welcome --to '{"email":"user@example.com"}' --data ./data.json
norq send transactional/welcome --to '{"email":"user@example.com","phone":"+1234567890"}' --channels email,sms
norq send transactional/welcome --to '{"email":"user@example.com","cc":["audit@example.com"]}' --channels email
norq send transactional/welcome --to '{"email":"user@example.com"}' --idempotency-key "order-shipped-ORD-123"
norq send transactional/welcome --to '{"email":"user@example.com"}' --dry-run
Flag Description
--to <json> Recipient as JSON (required) — supports email, phone, plus per-channel sub-shapes; for email, optional cc and bcc arrays
--data <file> Path to a JSON data file
--sample <name> Named sample from data.samples.yaml
--channels <a,b> Send only to these channels
--idempotency-key <key> Forwarded to providers that honor it (Resend, SuprSend) as the Idempotency-Key: header. When omitted, a UUID v4 is generated for this send and applied to every channel.
--dry-run Compile only, do not send
--json Output as JSON
--strict Force strict-mode runtime validation on (overrides config)
--no-strict Force strict-mode off (overrides config)

Strict mode is on by default. A Handlebars-inspired pre-send validator refuses to deliver when the template references data missing from the runtime data object, uses an unknown pipe, or passes a wrong-type pipe argument. This catches silent data-corruption bugs (typo’d variable names, etc.) before they reach the recipient. See Strict mode.

CLI push limitations. The CLI is for development. It can send push only via providers that use static bearer tokens (e.g. expo with an access token). It cannot:

  • Sign Apple JWT for APNs (also: ureq is HTTP/1.1 only — APNs requires HTTP/2).
  • Exchange Google service-account credentials for FCM access tokens.
  • Encrypt Web Push payloads.

For those providers, use an SDK. The CLI emits provider/cli-http2-required, provider/cli-oauth2-not-supported, or provider/cli-vapid-not-supported if you try.

Strict mode

Inspired by Handlebars’ strict mode. A pre-compile runtime validator refuses to send a notification when the template references data missing from the runtime data object, uses unknown pipes, or passes wrong-type pipe arguments.

On by default. Set strict: false in norq.config.yaml for a project-wide opt-out, or pass --no-strict per-invocation.

Legitimate exceptions that strict mode allows:

  • :::if path — paths used inside the consequent are implicitly guarded.
  • :::each items as item — loop binding is in scope inside the body.
  • {{ path | default "..." }} — the default pipe makes missing paths OK.
  • Explicit null{"field": null} in data is allowed (distinct from “missing field”).

Violations surface as:

  • undefined-runtime-path — expression path not in runtime data
  • unknown-pipe — pipe name not in KNOWN_PIPES
  • invalid-pipe-arg — pipe called with wrong type or arity

All three are reported with line numbers (remapped from expanded-doc coords through any partial expansions back to source coords).

norq dev

Start a preview dev server with hot reload.

norq dev                              # all notifications, port 3456
norq dev transactional/welcome        # specific notification
norq dev --port 8080       # custom port

The dev server provides a web UI where you can:

  • Switch between notifications and channels
  • Select different sample data
  • See live updates as you edit templates

norq doctor

Diagnose project environment, configuration, and health. Output is grouped into sections with three status levels: [✓] pass, [!] warning, [✗] error.

norq doctor                # human-readable grouped output
norq doctor --json         # machine-readable JSON (for CI/tooling)
Flag Description
--json Output as JSON

Sections and diagnostics:

Section Check Status Trigger Fix
Discovery Config found error No norq.config.yaml in cwd or subdirectories norq init
Discovery Multiple projects warn More than one norq.config.yaml discovered --config <path>
Environment Version pass Always shown
Environment Env vars error ${VAR} in config but VAR not set in environment export VAR=value
Project Config valid error/warn norq.config.yaml has schema errors or warnings Fix config syntax
Project Notifications pass Count of notifications by type folder
Project Routing gaps warn Channel has templates but no entry in routing Add routing.<channel>: <provider>
Project Providers warn No providers configured Add providers section
Health Lint error/warn norq lint finds errors or warnings norq lint
Health Schema coverage warn Notification missing data.schema.yaml Create schema file
Health Test coverage warn Notification missing tests.yaml Create tests file
Health Partials pass Lists _shared/*.md files
Brand brand.yaml pass/warn brand.yaml reachable, or compiled-in defaults in use norq brand init
Brand Resolver pass/warn Reports broken refs, unknown pipes, basedOn cycles norq brand show
Brand AGENTS_BRAND.md pass/warn Generated file matches current brand.yaml (only checked when the file already exists) norq brand sync-agents
Integrations Codegen warn Codegen output directory missing norq codegen

Example output:

Discovery
  [✓] Found norq project at ./

Environment
  [✓] norq v0.1.0-alpha.1

Project
  [✓] norq.config.yaml found and valid
  [✓] 3 notification(s) (1 promotional, 2 transactional)
  [✓] Routing: email → console, sms → console
  [✓] Providers: console

Health
  [✓] Lint: 0 error(s), 0 warning(s)
  [✓] Schema coverage: 3/3 notifications
  [!] Test coverage: 1/3 notifications
      Create tests.yaml in missing notification dirs
  [✓] Partials: email-footer, email-header

Project health: 1 warning(s)

Exit codes:

Code Meaning
0 All checks pass
1 One or more warnings
2 One or more errors

norq codegen

Generate type-safe SDK bindings from data schemas.

norq codegen                           # use config file targets
norq codegen --lang typescript --out src/generated/norq.ts
norq codegen --check                   # CI: verify files are up to date
Flag Description
--lang <language> Target language: typescript, python, go, java, ruby
--out <path> Output file path
--check Verify generated files match (for CI)

Without --lang/--out, reads targets from the codegen section of norq.config.yaml.

norq lsp

Start the Language Server Protocol server on stdin/stdout.

norq lsp

Used by editor extensions (VS Code, Neovim, etc.). Not typically run manually. See the editor setup page.

norq mcp-server

Start the Model Context Protocol server on stdin/stdout.

norq mcp-server

Used by AI agents and coding assistants. See the AI integration page.

norq add

Add notifications and partials from registries or Git repositories.

norq add welcome
norq add @acme/invoice --overwrite
norq add github:user/repo/notifications/transactional/welcome
norq add welcome invoice --dry-run

Accepts bare names (welcome), namespaced names (@acme/welcome), full URLs, or Git shorthand (github:owner/repo/path[@ref]). Multiple items can be added in one call.

Dependency resolution: When a registry item declares registryDependencies, norq add recursively fetches all transitive dependencies, topologically sorts them (dependencies before dependents), and writes files in dependency order. Partials are installed to the notification-local _shared/ directory. Circular dependencies are detected and reported as errors. Existing partials with the same name are not overwritten unless --overwrite is passed.

Flag Description
--overwrite Overwrite existing files without prompting
--skip Skip items that already exist locally
--dry-run Show what would be added without writing files
--path <path> Install to a specific directory instead of auto-detecting
--json Output as JSON

norq registry

Manage and query notification registries. Subcommands:

norq registry build

Build registry.json into static JSON files for hosting.

norq registry build
norq registry build --out dist/registry --json
Flag Description
--out <dir> Output directory (default: ./public/r)
--json Output as JSON

norq registry validate

Validate a registry manifest and all referenced items.

norq registry validate
norq registry validate --json
Flag Description
--json Output as JSON

Search across configured registries.

norq registry search welcome
norq registry search invoice --type transactional
norq registry search --category account
Flag Description
--type <type> Filter by notification type (system, transactional, promotional)
--category <cat> Filter by category
--json Output as JSON

norq registry list

List all items in configured registries.

norq registry list
norq registry list --type promotional --json
Flag Description
--type <type> Filter by notification type
--json Output as JSON

norq registry view

View details of a specific registry item.

norq registry view welcome
norq registry view @acme/invoice --files --json
Flag Description
--files Include file contents in output
--json Output as JSON

norq provider

Manage providers and channel routing. Subcommands:

norq provider list

List all registered providers and their configuration status.

norq provider list

Output columns:

Column Description
TYPE Provider type (built-in or custom)
NAME Provider identifier
CHANNELS Channels the provider supports (all or specific channels)
STATUS See below

STATUS values:

Value Meaning
configured (config) Built-in provider has a providers.<name>.config block in norq.config.yaml and credentials resolved cleanly
configured (env) Built-in provider isn’t in norq.config.yaml but NORQ_<NAME>_* env vars are set
configured Custom provider with credentials resolved (or always-available built-ins like console)
credentials missing Config block present but a required field (e.g. ${RESEND_API_KEY}) is empty
invalid config Config block is malformed (e.g. config: is a string instead of a mapping)
not configured Built-in provider has neither a config block nor env vars set
Built-in providers
Provider Channels Config keys (providers.<name>.config) Env-var fallback
resend email api_key, from, from_name (optional), reply_to (optional, scalar or list) NORQ_RESEND_API_KEY, NORQ_RESEND_FROM_ADDRESS
sendgrid email api_key, from, from_name (optional), reply_to (optional, scalar or list) NORQ_SENDGRID_API_KEY, NORQ_SENDGRID_FROM_ADDRESS
suprsend all workspace_key, workspace_secret, base_url (optional) NORQ_SUPRSEND_WORKSPACE_KEY, NORQ_SUPRSEND_WORKSPACE_SECRET, NORQ_SUPRSEND_BASE_URL
console all (none — always available) (none)
fcm push (ios, android, web) projectId, serviceAccount (file path, ${ENV}, or inline JSON) NORQ_FCM_PROJECT_ID, NORQ_FCM_SERVICE_ACCOUNT
apns push (ios) keyId, teamId, bundleId, keyPath or keyPem, production NORQ_APNS_KEY_ID, NORQ_APNS_TEAM_ID, NORQ_APNS_BUNDLE_ID, NORQ_APNS_KEY_PATH (or NORQ_APNS_KEY_PEM), NORQ_APNS_PRODUCTION
expo push (ios, android) accessToken (optional) NORQ_EXPO_ACCESS_TOKEN
webpush push (web) vapidPublicKey, vapidPrivateKey, subject NORQ_WEBPUSH_VAPID_PUBLIC_KEY, NORQ_WEBPUSH_VAPID_PRIVATE_KEY, NORQ_WEBPUSH_SUBJECT

Config-driven setup is preferred. Example:

# norq.config.yaml
providers:
  resend:
    config:
      api_key: ${RESEND_API_KEY}
      from: norq@alerts.example.com
      from_name: "Example"
      reply_to: support@example.com
routing:
  email: resend

Env-var fallback exists for backward compatibility — when providers.resend is absent from the config, the CLI falls back to NORQ_RESEND_* env vars and reports configured (env).

norq provider routing

Show the channel-to-provider routing table. Displays which provider handles each channel and why.

norq provider routing

Output columns:

Column Description
CHANNEL Channel name (email, sms, slack, push, whatsapp, msteams)
PROVIDER Provider that will handle sends for this channel
REASON How the provider was selected (first match or no provider available)

Resolution order: explicit routing config in norq.config.yaml takes priority, then first-match by registration order.

norq fmt

Format all template files in the project with canonical indentation and spacing.

norq fmt                   # format all .md files in notifications/
norq fmt --check           # check formatting without writing (exit 1 if changes needed)
Flag Description
--check Check only – report files that need formatting, exit 1 if any do

The formatter enforces:

  • 2-space indent inside directives, +2 for each nesting level
  • One blank line between block-level elements
  • No trailing blank lines before ::: closers
  • No multiple consecutive blank lines
  • Frontmatter and code blocks preserved as-is

Use --check in CI to enforce consistent formatting:

# GitHub Actions
- run: norq fmt --check

The LSP also supports document formatting (Format Document) using the same rules.

norq brand

Manage the project’s brand.yaml – the tenant’s visual identity (colors, typography, spacing, radii, styles, fonts, voice). Five subcommands cover the authoring lifecycle:

norq brand init                       # scaffold brand.yaml from defaults
norq brand show [--json]              # print resolved brand (aliases + pipes flattened)
norq brand import <file> [--out X]    # convert DESIGN.md / DTCG / Figma / Style Dictionary
norq brand export --format <fmt>      # emit dtcg | tailwind | css | json
norq brand diff <left> <right>        # semantic diff between two brand files

norq brand init

norq brand init                       # writes <config-dir>/brand.yaml
norq brand init --path ./design/brand.yaml
norq brand init --force               # overwrite an existing file
Flag Description
--path <P> Output path. Defaults to <config-dir>/brand.yaml.
--force Overwrite an existing brand.yaml. Refuses without it.

The scaffolded file is the compiled-in default brand (mirrors the previous flat theme: palette). Edit it to customise tokens.

norq brand show

norq brand show                       # human-readable summary
norq brand show --json                # ResolvedBrand JSON for scripting

Prints the brand the project will compile against – aliases like {colors.brand} and color pipes like {colors.brand | darken 10%} are flattened to literal values. Diagnostics from the resolver (broken refs, unknown pipes, basedOn cycles) are listed at the end.

norq brand import

norq brand import DESIGN.md                 # writes <config-dir>/brand.yaml
norq brand import tokens.json --out custom.yaml
norq brand import figma.json --stdout       # print YAML, don't write
Flag Description
--out <P> Where to write brand.yaml. Defaults to <config-dir>/brand.yaml.
--force Overwrite an existing brand.yaml.
--stdout Emit the resulting YAML to stdout instead of a file.

Format detection is content-driven first, extension-only as a tiebreaker. Recognised inputs:

Format Detected by
DESIGN.md .md extension
DTCG JSON $schema field or any descendant $value field
Figma Tokens Studio nested groups with {value, type} leaves
Style Dictionary top-level color / size / font / radius groups
Norq brand.yaml a YAML doc with meta: / tokens: / voice: keys

The DESIGN.md importer is regex-driven over Markdown. It pulls colors from bullet lines that contain a hex literal, dimensions from numbered bullets under ## Spacing / ## Border radius headers, and voice principles from bullets under ## Voice / ## Tone / ## Principles. Hit rate varies by source – the importer prints per-section counts so you can see what was recognised; missing groups stay empty for manual fill-in.

norq brand export

norq brand export --format dtcg              # to stdout
norq brand export --format tailwind --out tailwind.config.js
norq brand export --format css --out brand.css
norq brand export --format json
Format Output
dtcg DTCG 2025.10 JSON. Norq-specific bits (styles, voice, fonts, modes, defaults, codeTheme) emitted under $extensions.norq for round-trip parity.
tailwind tailwind.config.js stub with theme.extend.colors, fontSize, fontFamily, spacing, borderRadius populated.
css :root { --colors-brand: #...; } custom-property block. Dark-mode overrides emit a @media (prefers-color-scheme: dark) block.
json Norq’s own brand.yaml shape, JSON-encoded.

Pipe expressions ({colors.brand | darken 10%}) are pre-resolved before export. DTCG has no concept of computed tokens, so the exported file is a snapshot – a subsequent brand import reads the literal hex, not the pipe expression.

norq brand diff

norq brand diff old.yaml new.yaml

Semantic diff: walks colors, spacing, radii, typography structurally and reports add (+), remove (-), and change (~) per token. Token reorderings inside the YAML produce no noise – only token-name and value changes show up. Defaults are NOT merged; the diff shows only what is literally written in each file.

norq brand sync-agents

Generates AGENTS_BRAND.md – a single Markdown file every AI agent reads – from the resolved brand. The file lives at the project root (next to norq.config.yaml) and is regenerated on demand. Source-of-truth is brand.yaml; AGENTS_BRAND.md is a human- and agent-readable derivative.

norq brand sync-agents                      # write <config-dir>/AGENTS_BRAND.md
norq brand sync-agents --check              # CI mode: exit 1 if regen would change the file
norq brand sync-agents --stdout             # print, don't write
norq brand sync-agents --scope rules        # subset
norq brand sync-agents --out X.md           # custom path
Flag Description
--check Exit non-zero with a remediation hint if the file on disk differs from what would be regenerated. Use in pre-commit hooks or CI.
--stdout Emit the rendered Markdown to stdout instead of writing to disk. Pipe straight into a system prompt with --scope rules.
--out <P> Override the output path. Defaults to <config-dir>/AGENTS_BRAND.md.
--scope <s> One of rules (principles only – smallest payload), brand (voice + principles), tokens (token reference + heading mapping + quickview), all (everything; default).

The output is deterministic – same brand + same scope produces the same bytes – so --check does a byte-for-byte comparison without depending on diff tooling.

norq doctor runs the same check and warns when AGENTS_BRAND.md is stale relative to brand.yaml. norq init appends a ## Visual identity pointer to existing AGENTS.md / CLAUDE.md files at the project root (it does not create them). The pointer is stable text and never churns – the regeneratable file carries the churn.

norq completions

Generate shell completions for bash, zsh, fish, elvish, or PowerShell.

norq completions bash >> ~/.bashrc
norq completions zsh >> ~/.zshrc
norq completions fish > ~/.config/fish/completions/norq.fish
norq completions elvish >> ~/.elvish/rc.elv
norq completions powershell >> $PROFILE
Argument Description
<shell> Shell to generate completions for: bash, zsh, fish, elvish, powershell

norq version

Show version information.

norq version
norq version --json

Global flags

These flags work on all commands:

Flag Description
--config <path> Explicit path to norq.config.yaml. Skips automatic discovery.

Configuration

All project configuration lives in norq.config.yaml:

notifications: ./notifications
 
providers:
  resend:
    config:
      api_key: ${RESEND_API_KEY}
      from: "notifications@myapp.com"
 
routing:
  email: resend
  sms: suprsend
 
codegen:
  - lang: typescript
    out: src/generated/norq.ts

Environment variables use ${VAR_NAME} syntax and are substituted at load time.

Providers

Providers handle sending notifications to channels and rendering previews. Norq ships with seven built-in providers — three general-purpose plus four push-specialist:

Provider Channels Render Required env vars
suprsend all preview, screenshot NORQ_SUPRSEND_WORKSPACE_KEY, NORQ_SUPRSEND_WORKSPACE_SECRET
resend email NORQ_RESEND_API_KEY, NORQ_RESEND_FROM_ADDRESS
sendgrid email NORQ_SENDGRID_API_KEY, NORQ_SENDGRID_FROM_ADDRESS
console all none (always available, prints to stderr)
fcm push (ios, android, web) NORQ_FCM_PROJECT_ID, NORQ_FCM_SERVICE_ACCOUNT
apns push (ios) NORQ_APNS_KEY_ID, NORQ_APNS_TEAM_ID, NORQ_APNS_BUNDLE_ID, NORQ_APNS_KEY_PATH (or NORQ_APNS_KEY_PEM)
expo push (ios, android) NORQ_EXPO_ACCESS_TOKEN (optional)
webpush push (web) NORQ_WEBPUSH_VAPID_PUBLIC_KEY, NORQ_WEBPUSH_VAPID_PRIVATE_KEY, NORQ_WEBPUSH_SUBJECT

Built-in providers are auto-registered when their env vars are set. Console is always registered as a fallback.

Custom providers

Define custom providers under providers: in norq.config.yaml. A custom provider specifies which channels it supports, authentication, and HTTP request definitions for each operation:

providers:
  mailgun:
    channels: [email]
    config:
      domain: { required: true }
      api_key: { required: true, secret: true }
    auth:
      type: basic
      username: api
      password: "{{secrets.api_key}}"
    send:
      method: POST
      url: "https://api.mailgun.net/v3/{{config.domain}}/messages"
      body:
        from: "notifications@{{config.domain}}"
        to: "{{input.recipient}}"
        subject: "{{input.subject}}"
        html: "{{input.html}}"
      response:
        success_status: [200]
        message_id_path: "$.id"

Multi-channel custom providers use a per-channel send map:

providers:
  internal-gateway:
    channels: [email, sms]
    config:
      base_url: { required: true }
      api_key: { required: true, secret: true }
    auth:
      type: bearer
      token: "{{secrets.api_key}}"
    send:
      email:
        method: POST
        url: "{{config.base_url}}/v1/email"
        body:
          to: "{{input.recipient}}"
          subject: "{{input.subject}}"
          html: "{{input.html}}"
      sms:
        method: POST
        url: "{{config.base_url}}/v1/sms"
        body:
          to: "{{input.recipient}}"
          text: "{{input.body}}"

Auth types: bearer (token), basic (username/password), api_key (header or query param with in, key, value).

Config values marked secret: true are resolved from NORQ_<PROVIDER>_<KEY> env vars (uppercased). Non-secret config uses the same convention.

Routing

The routing: section maps channels to providers explicitly. Without explicit routing, Norq uses first-match by registration order (built-ins first, then custom providers in config order).

routing:
  email: mailgun
  sms: internal-gateway
  slack: suprsend
  push: fcm
  push.ios: apns          # platform-scoped routing wins over channel-wide push

Use norq provider routing to inspect the resolved routing table and see which provider handles each channel.

Provider errors

Error Cause Fix
Unknown provider: {name} The provider name in routing: doesn’t match any built-in or custom provider Check spelling in routing: config. Run norq provider list to see available providers
Provider '{provider}' error: {message} The provider returned an error during request preparation or sending Check provider credentials and configuration. The {message} contains details from the provider
No provider configured for channel '{channel}' No provider is available to handle this channel Add a provider that supports this channel, or add a routing: entry pointing to one
Provider '{provider}' does not support op '{op}' The provider doesn’t implement the requested operation (e.g. render preview) Use a different provider. For render ops, SuprSend is currently the only built-in with render support
Provider '{provider}' does not support channel '{channel}' The provider was routed a channel it can’t handle Fix routing: to map this channel to a provider that supports it
Provider '{provider}' missing required config: {message} A required config field is not set Set the required env var. Run norq provider list to check configuration status

Exit codes

Code Meaning
0 Success
1 Error (lint errors, compilation failure, etc.). norq doctor: warnings present
2 norq doctor only: errors present