cli

module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: MIT

README

🍋 nimbu - the official Nimbu CLI

CI Go Report Card

Fast, AI-agent friendly CLI for the Nimbu API.

Agent Skill

This CLI is available as an open agent skill for AI assistants including Claude Code, OpenClaw, Codex, Cursor, GitHub Copilot, and 35+ agents.

npx skills add nimbu/cli

Features

  • Broad API coverage - Channels, pages, products, orders, customers, themes, and more
  • Admin workflows - Domains, sender verification, and support actions for orders/customers
  • Secure credentials - OS keychain storage (macOS Keychain, Linux Secret Service)
  • JSON-first output - --json and --plain (TSV) modes for scripting
  • Agent-friendly - Command allowlists, readonly mode, deterministic output
  • Shell completions - Bash, Zsh, Fish

Installation

Homebrew (macOS/Linux)
brew install nimbu/tap/nimbu
Go
go install github.com/nimbu/cli/cmd/nimbu-cli@latest
Download Binary

Download from GitHub Releases.

Available for:

  • macOS: Intel & Apple Silicon (tar.gz)
  • Linux: amd64 & arm64 (tar.gz)
  • Windows: amd64 & arm64 (zip)
Build from Source
git clone https://github.com/nimbu/cli.git
cd cli
make build
./bin/nimbu --help

Quick Start

# Log in to your Nimbu account in the browser
nimbu auth login

# List your sites
nimbu sites list

# Bootstrap a local theme project (interactive wizard)
nimbu init

# ...or answer every step up front (CI, scripts, agents)
nimbu init --no-input --site my-site --theme default my-theme

# Help for any command or group
nimbu help channels entries

# Work with a specific site
nimbu channels list --site my-site

# Manage custom domains
nimbu domains list --site my-site

# Verify a sender domain
nimbu senders verify-ownership --sender mail.example.com --site my-site

# JSON output for scripting
nimbu channels entries list --channel blog --site my-site --json | jq '.[]'

# Start local simulator proxy + project dev server
nimbu server

CLI Grammar

This v0.x line intentionally makes a clean grammar cut: resource identity moved from positional IDs to explicit flags. Existing scripts using positional identity must be migrated before upgrading to this grammar.

Commands are flag-first for resource identity. Put identifiers in flags such as --site, --channel, --entry, --page, --menu, --field, --product, --key, --domain, --sender, --from, and --to.

Payload stays last. Inline assignments trail identity flags and command options:

nimbu channels entries update --channel blog --entry welcome title="Welcome" published:=true
nimbu pages update --page about --file payload.json
nimbu sites settings --site staging --json

Upgrade Notes

Behavior changes that can affect scripts:

  • stdout carries results only. List footers ("Showing 25 of 100 ..."), warnings and update notices go to stderr. With --json or --plain, an incomplete listing adds one stderr line such as nimbu: showing 25 of 327 products; pass --all or --page 2; stdout keeps its shape.
  • --plain escapes fields (see Output and Exit Codes).
  • Prompts never block a pipe: without a terminal on stdin, with CI set, or with --no-input, a confirmation fails with cli.confirmation_required (exit 2) instead of waiting. Pass --force or --yes.
  • Ctrl-C and SIGTERM exit 130 (the nimbu init wizard and the nimbu config banner picker exit 143 on SIGTERM). Long-running commands that stop gracefully on the first signal (nimbu server, apps logs --tail, channels entries watch) exit 0; a second Ctrl-C force-stops them with 130.
  • In the nimbu init wizard, NIMBU_SITE preselects the site but no longer skips the question; --site or --env on the command line still does. With --no-input, NIMBU_SITE answers it.

pages update --file now merges by default. Use --replace with a file payload when existing automation depends on omission removing page content.

Migration examples:

# Old: nimbu channels get blog
nimbu channels get --channel blog

# Old: nimbu channels entries update blog welcome title=Welcome
nimbu channels entries update --channel blog --entry welcome title=Welcome

Inline Payload Syntax

Most create and update commands accept inline assignments in addition to --file JSON payloads.

Operators:

  • key=value - string value
  • key:=json - typed JSON value (number, bool, object, array, null)
  • key=@file.txt - read raw file content as string
  • key:=@file.json - read and parse JSON from file

Use dot paths for nesting:

nimbu products update --product product-123 name="Wine Box" price:=29.9 seo.title="Gift box"

--file and inline assignments are mutually exclusive.

# File payload
nimbu pages update --page about --file payload.json

# Inline payload
nimbu pages update --page about title="About us" published:=true

For richer document resources, inline updates stay intentionally shallow:

  • pages update accepts title, template, published, locale, or one complete top-level translations object
  • menus update accepts name, handle, or one complete top-level translations object
  • deep/nested page edits should use pages set|insert|move|delete-block or pages batch; pages update --file remains the whole-document fallback. Deep menu edits should use --file or stdin JSON

Rich Resource Contracts

pages, menus, and channels have resource-specific contracts rather than generic CRUD payloads.

  • pages get and pages update use page fullpath as the canonical identifier
  • Default page recipe: pages schema then pages get --shape (ids, positions, select options), then pages set|insert|move|delete-block or pages batch
  • Human paths are 0-based (Blokken[2].Title, Blokken[id=<oid>], Blokken[slug=hero]); a leading / is a raw API path
  • pages get --page <fullpath> --json returns the full page document, including nested items
  • pages get --page <fullpath> --download-assets DIR --json downloads file editables and rewrites them to attachment_path
  • pages update --page <fullpath> merges by default (replace-safe); --dry-run prints the merged body without a request
  • pages create / pages update accept security_mechanism (none|humans|customers) and published
  • Drafts: --draft on the surgical verbs, then pages draft preview-url / publish / discard; nimbu server --draft <page> for local preview
  • menus get --menu <slug> --json returns the full nested menu tree (recovers via ?nested=1 when needed)
  • menus update --file replaces the nested item tree: items are kept by id, others get explicit tombstones, and the write stops before any PATCH if the server omits item ids; inline name/handle updates are shallow (no items rewrite)
  • Menu items need a kind (url with target_url, page with target_fullpath, or channel); an empty kind is inferred from the target and any other value fails locally
  • channels get --channel <slug> --json returns the richer channel contract, including schema/customizations and ACL-oriented fields

Examples:

# Learn the page skeleton (ids, positions, select options)
nimbu pages schema --page about/team --json
nimbu pages get --page about/team --shape

# Surgical edit (preview the op, then write)
nimbu pages set --page about/team --path title --dry-run --diff "Our Team"
nimbu pages set --page about/team --path Blokken[0].Title "Fast"
nimbu pages insert --page about/team --path Blokken --slug item --position 0
nimbu pages batch --page about/team --file ops.json

# Whole-document fallback
nimbu pages get --page about/team --json
nimbu pages get --page about/team --download-assets tmp/page-assets --json
nimbu pages update --page about/team --file page.json --dry-run
nimbu pages update --page about/team --file page.json

# Draft -> preview -> publish (409 draft_base_changed: --confirm or discard --force)
nimbu pages set --page about/team --path title --draft "Coming soon"
nimbu pages draft preview-url --page about/team --open
nimbu pages draft publish --page about/team

# Nested menu fetch
nimbu menus get --menu main --json

# Rich channel contract with schema and ACL data
nimbu channels get --channel articles --json

# Create a channel from a full JSON file (name, slug, title_field, customizations[])
nimbu channels create --file articles.json --json

# Create a channel from inline assignments (customizations as typed JSON)
nimbu channels create name=Articles slug=articles title_field=title customizations:=@fields.json

# Delete a whole channel definition (requires --force)
nimbu channels delete --channel articles --force
Channel field workflows

channels fields manages a channel schema with the same grammar: identity in flags, payload as trailing assignments or --file.

# Inspect fields
nimbu channels fields list --channel blog --json

# Add or update one field
nimbu channels fields add --channel blog --name summary type=string label="Summary"
nimbu channels fields update --channel blog --field summary label="Teaser" required:=true

# Delete one field
nimbu channels fields delete --channel blog --field summary --force

# Apply a partial schema patch from JSON
nimbu channels fields apply --channel blog --file fields.patch.json

# Replace the full field set from JSON
nimbu channels fields replace --channel blog --file fields.json --force

# Compare the current schema with a local JSON array
nimbu channels fields diff --channel blog --file fields.json --json
Channel types for cloud code

channels types writes one TypeScript module with a type for every channel of the site. Field types use the nimbu-js-sdk helpers (ReferenceTo<Authors>, Select<'draft' | 'live'>, NimbuFile, ...), and the module augments NimbuChannels in nimbu-js-sdk/cloud, so cloud code callbacks get a typed req.object for those channels.

# Print the module for all channels
nimbu channels types

# Write it into the project (parent directories are created)
nimbu channels types --output code/types/nimbu-channels.ts

# Only some channels; references to channels left out become untyped
nimbu channels types --channel blog --channel authors -o code/types/nimbu-channels.ts

Without --channel the module covers the whole site, so it also sets strictChannels: true in NimbuCloudOptions: a callback on a slug that is not a channel of the site becomes a type error. A --channel subset leaves it out, and slugs outside the module stay accepted but untyped.

Cloud types are opt-in. Include the generated file in your tsconfig.json and add "types": ["nimbu-js-sdk/cloud"] (or a /// <reference types="nimbu-js-sdk/cloud" />). Use a .ts file name rather than .d.ts: with skipLibCheck (the tsc --init default) TypeScript does not check .d.ts files, so a broken import there turns field types into any without an error. The module holds only types, and apps push uploads **/*.js by default, so the file never reaches the server.

The output is sorted by slug and carries no timestamp, so regenerating only shows real schema changes in a diff. --json returns {site, channels, typescript} (or path instead of typescript with --output). channels info --channel <slug> --typescript renders the same type for a single channel.

What the types promise:

  • Optional fields read as T | null: the API sends null for blank values. Booleans are always true or false. Required fields are non-null once the entry passed validation; a field that is only required under a condition (required_expression) is optional.
  • customer fields are the customer as plain JSON, or its id when the embed is not allowed (JSONField | string). A belongs_to reference to customers is a NimbuCustomer; references to other native types (products, orders, pages, articles) stay untyped, as does any reference whose target is also a channel slug.
  • Select unions list option names in the API's locale. On a multilingual site, code running in another locale can see translated names.
  • Type names are the PascalCase slug. When two slugs collide (blog-posts and blog_posts), the one that sorts first keeps the plain name and the other gets a name from its own spelling (Blog_Posts).
Translations shorthand

translations create and translations update support locale shorthand: top-level locale keys are mapped to values.<locale>.

nimbu translations update --key activate.label.lastname nl=Achternaam
nimbu translations update --key activate.label.lastname values.fr=Nom

Locale keys are validated and canonicalized (nl_BE → nl-BE, zh_hant_tw → zh-Hant-TW). translations create --file accepts either one translation object or an array for batch creation.

Localized content

For localized resources, --locale selects the content locale and is sent as content_locale. This applies to pages, products, collections, blogs and posts, menus, notifications, channel entries, and shipping rates.

# Update one locale without touching the default locale
nimbu pages update --page about --locale nl --file nl.json

# Update several locales in one request
nimbu pages update --page about --file translations.json

nl.json can contain {"seo_title":"Nederlandse titel"}. translations.json can contain:

{
  "translations": {
    "nl": {"seo_title": "Nederlandse titel"},
    "fr": {"seo_title": "Titre français"}
  }
}

Prefer --file for nested page content and multi-locale payloads. A complete top-level map is also supported as translations:=@translations.json; other deep page edits remain file-only. Inline updates send only the assigned keys; the fetched document (whose translations map is locale-resolved with fallbacks) is never written back, and fullpath, public_url, and depth are always stripped from writes. To set a localized slug, pipe a minimal document: echo '{"slug":"about-us"}' | nimbu pages update --page over-ons --locale en --file -. JSON output always preserves the complete API response, including every translations map. Human and plain output recursively overlay the selected locale and fall back to the default value when a translated field is absent.

Commands

nimbu --help is the authoritative list; this is its overview:

Getting started:
  nimbu auth                    Authentication and credentials
  nimbu init                    Bootstrap a local theme project
  nimbu sites                   Manage sites
  nimbu server                  Run local simulator proxy with child dev server
  nimbu env                     Manage project environments
  nimbu config                  Manage configuration

Content:
  nimbu channels                Manage channels and entries
  nimbu pages                   Manage pages
  nimbu menus                   Manage navigation menus
  nimbu blogs                   Manage blogs
  nimbu translations            Manage translations
  nimbu uploads                 Manage uploads
  nimbu mails (mail)            Sync notification templates to local files
  nimbu notifications           Manage notifications

Commerce:
  nimbu products                Manage products
  nimbu orders                  Manage orders
  nimbu customers               Manage customers
  nimbu coupons                 Manage coupons
  nimbu collections             Manage collections
  nimbu shipping-rates          Manage shipping rates

Theme & code:
  nimbu themes                  Manage themes
  nimbu schema                  Manage declarative schemas
  nimbu release                 Release schema, cloud code and theme
  nimbu releases                List successful releases
  nimbu functions               Execute cloud functions
  nimbu jobs                    Execute cloud jobs
  nimbu apps                    Manage OAuth apps

Admin:
  nimbu domains                 Manage custom domains
  nimbu domain-registrations    Manage domain registrations
  nimbu senders                 Manage email sender domains
  nimbu settings                Manage site settings
  nimbu roles                   Manage roles
  nimbu redirects               Manage redirects
  nimbu webhooks                Manage webhooks
  nimbu announcements           Manage platform announcements
  nimbu events                  Track and ingest events
  nimbu realtime                Realtime grants and live queries

Advanced:
  nimbu api                     Raw API access
  nimbu commands                Export the machine-readable command contract
  nimbu completion              Generate shell completions (bash, zsh, fish)

nimbu help <command>, nimbu <command> --help and nimbu <group> help print the same help; a bare group such as nimbu channels lists its subcommands. With --json/--plain (or NIMBU_JSON/NIMBU_PLAIN) a bare group is a usage error (exit 2) instead, so stdout never carries help text a script would parse. Default help hides output, agent and connection flags; add --help-all to see them. Commands that can stop for a confirmation list --force, --yes and --dry-run under Safety.

Load completions with eval "$(nimbu completion zsh)" in ~/.zshrc (or bash/fish; --shell zsh still works).

Completion debug output is normally hidden by shell wrappers. Set NIMBU_COMPLETION_DEBUG=1 and inspect the completion-debug.log file in the Nimbu data directory when debugging dynamic completions.

Output and Exit Codes

--json prints the bare JSON value on stdout. --plain prints one tab-separated record per line. Inside a field, a backslash becomes \\, a tab \t, a line feed \n and a carriage return \r, so every record stays on one line with a fixed field count; decode by replacing those four sequences left to right. themes diff --content --plain adds the unified diff as an escaped third field. functions run --plain is the exception: it prints the function result as one compact JSON object per line.

Human tables on a terminal are fitted to its width: free-text columns such as NAME or TITLE give way first, then URLs and timestamps, and identifier columns (ID, KEY, SLUG, FULLPATH, EMAIL, ...) are never cut. Pass --wide (or set NIMBU_WIDE=1) to print every value in full. Piped, --plain and --json output is never truncated. Control characters in API data (ESC, BEL, ...) show as � in human output so they cannot drive the terminal.

Colors follow, highest first: --color always|never (or NIMBU_COLOR), NO_COLOR, TERM=dumb, FORCE_COLOR=0, then whether the stream is a terminal. FORCE_COLOR=1 and CLICOLOR_FORCE are ignored on purpose: Node tooling exports them shell-wide, and honoring them would leak escape codes into pipes and agent logs. Use --color always to force color. NIMBU_ASCII=1 (also TERM=dumb or a non-UTF-8 locale) swaps symbols such as ✓ and … for ASCII. Under TERM=dumb, nimbu init asks with line prompts instead of its full-screen wizard, and nimbu config banner refuses to start. Line output uses a palette tuned for dark backgrounds unless NIMBU_THEME=light is set or COLORFGBG reports a light background; NIMBU_THEME=dark forces the dark palette.

Exit Meaning
0 Success
1 General error
2 Usage error: bad flags, no site (cli.usage.site_required), a confirmation nobody could give (cli.confirmation_required), or a write refused by --readonly/NIMBU_READONLY (cli.readonly)
3 Not logged in / authentication failed, or no usable credential store (auth.keyring_unavailable)
4 Forbidden or missing scope
5 Not found
6 Validation error
7 Rate limited (retryable)
8 Network error (retryable)
130 Interrupted (Ctrl-C or SIGTERM); a second Ctrl-C exits at once. server, apps logs --tail and channels entries watch exit 0 when the first signal stops them gracefully
143 The nimbu init wizard or nimbu config banner picker was stopped with SIGTERM

With --json, errors (usage errors too) are a JSON envelope on stderr with code, hint, exit_code and retryable.

Admin Workflow Commands

# Make a domain primary
nimbu domains make-primary --domain shop.example.com --site my-site --force

# Trigger sender verification
nimbu senders verify --sender mail.example.com --site my-site

# Record manual payment for an order
nimbu orders pay --order 100012 --site my-site

# Resend customer confirmation
nimbu customers resend-confirmation --customer alice@example.com --site my-site

# Empty a channel with strict confirmation
nimbu channels empty --channel news --site my-site --confirm news --force

# Copy an existing Nimbu upload through FileRef
nimbu uploads create --site target-site --file-ref nimbu://archive-site/uploads/507f1f77bcf86cd799439014

Declarative Schemas and Releases

Deploy the Rails schema and release API before using these commands. Run them from a project containing nimbu.yml:

nimbu env add staging --host api.nimbu.io --site acme-staging
nimbu env add production --host api.nimbu.io --site acme --protected
nimbu schema pull --env staging
# Edit and commit schema/*.yml and schema/{channels,blogs,checkout_profiles}/*.yml
nimbu schema plan --env production
nimbu schema apply --env production
nimbu release --env production --dry-run
nimbu release --env production

schema pull reads each target's canonical /schema endpoint into schema/. Use --env to select host and site together; it takes precedence over explicit site and API URL flags. A release applies schemas, pushes cloud code, then pushes the theme. Commit your changes first; use --allow-dirty for a deliberate uncommitted deployment.

Protected environments require confirmation. In CI, pass --no-input --yes to schema apply or release; --force does not bypass protection. Elsewhere --yes (-y) answers the same confirmations as --force, but it never forces an overwrite: theme conflicts with remote edits, existing output files and sites copy conflicts still need --force, so release --yes stops on a remote theme edit. Without a terminal on stdin, or with CI set, nimbu never prompts: it fails with cli.confirmation_required (exit 2) and names the flag to pass. Review plan before adding --prune, which removes omitted fields and options. Creating circular channel references may leave placeholder fields; remove those with a reviewed --prune apply.

Channel files carry the channel's access rules as acl, with one scope (public, shared, private or none) per action:

acl:
  create: none
  read: public
  update: private
  delete: none

Actions you leave out keep their current value. plan reports each change as an acl.<action> operation. Narrowing access is safe; widening it is destructive and needs the same confirmation as other destructive changes.

Fingerprints reject stale plans, and declarative schema applies are serialized per site. Avoid concurrent admin or legacy API schema edits during deployment.

After a failure, inspect the reported completed and failed targets or stages, then re-plan before retrying. Earlier changes remain applied. There is no automatic rollback: deploying an older commit does not reverse a field rename or recover pruned data.

Advanced Workflows

# Native settings
nimbu settings update --section shipping --site my-site bpost_label_qty:=2

# Whole consent configuration
nimbu settings consent config get --site my-site --json
nimbu settings consent config update --site my-site enabled:=true
nimbu settings consent config replace --site my-site --file consent.json --force
nimbu settings consent config copy --from staging --to production --dry-run

# Shipping rates; translations use one write per locale
nimbu shipping-rates list --site my-site --json
nimbu shipping-rates create --site my-site name=Standard criteria=weight price:=7.5 region_id=REGION_ID
nimbu shipping-rates update --site my-site --rate RATE_ID --locale nl name="Standaard"
nimbu shipping-rates delete --site my-site --rate RATE_ID --force

# Role membership. Prefer add/remove/set over a raw customers array.
nimbu roles customers add --role bingo --site my-site --customer 6aa173bcac852eb6438192f1
nimbu roles customers remove --role bingo --site my-site --customer 6aa173bcac852eb6438192f1
nimbu roles customers set --role bingo --site my-site --customer 6aa173bcac852eb6438192f1
nimbu roles update --role bingo --site my-site --dry-run --file role.json
nimbu roles update --role bingo --site my-site --force --file role.json

# Media and version history
nimbu products attachments download --product PRODUCT --attachment ATTACHMENT --output manual.pdf
nimbu pages versions list --page about --site my-site

# Live queries over the realtime socket
nimbu channels entries watch --channel blog --site my-site --json
nimbu channels entries watch --channel blog --filters status=published --for 30s --once
nimbu realtime grant --site my-site --json

# Raw API remains an escape hatch
nimbu api patch /unsupported_endpoint --data @payload.json

# Legacy raw syntax remains compatible
nimbu api --method GET --path /subscriptions --site my-site

Shipping rates expose list, get, create, update, and delete; there is no count or copy. The API does not accept a nested translations payload for this resource, so create once and repeat update --locale for each language. region_id is intentionally opaque: regions are site-specific and are not exposed as a public CLI resource.

roles update treats customers, children, and parents as a full replace when you send a JSON array. Shrinking any of those relations by more than half requires --force. To clear a relation, send an empty array ({"customers": []}) with roles update --force; roles customers set always requires at least one --customer. --dry-run prints the request body and the projected member counts without writing. Prefer roles customers add|remove|set: those commands GET the role, PUT the full customer ID list, then GET again to verify. The same --force rule applies when remove or set would drop more than half of the members.

Relation __op envelopes (AddReference, RemoveReference, Batch, and the AddRelation / RemoveRelation aliases) are a server-side feature. The CLI sends them through verbatim and does not rewrite them into a local patch.

Accounts, regions, product types, and vendors are internal API resources and are intentionally not exposed as public CLI commands.

Realtime

nimbu channels entries watch --channel <channel> opens a websocket to the site and streams entry changes as they happen. It mints a short-lived, single-use grant behind the scenes; the grant is never printed. nimbu realtime grant exposes the same grant for custom clients.

nimbu channels entries watch --channel blog --json
nimbu channels entries watch --channel blog --filters status=published --filters author.ne=bob
nimbu channels entries watch --channel blog --where "published=true" --for 2m
nimbu channels entries watch --channel blog --once --json
nimbu channels entries watch --channel blog --host localhost:3000 --insecure   # dev

Live queries are a filter, not a query language. --filters and --where use the same grammar as channels entries list, but pagination, sorting, projection, search and geo/regex operators are rejected locally before the socket opens (exit code 2). --for stops watching after a duration; the global --timeout stays the HTTP request timeout.

With --json, stdout carries nothing but raw server event envelopes, one compact JSON object per line, so the stream pipes straight into jq -c. Status, reconnect and control lines always go to stderr.

Field Type Notes
event_id string Unique per delivery; duplicates are dropped client-side
event string added, changed, removed, or resync
resource string Always channel_entries
parent_id string The channel you subscribed to
id string Entry ID; may be absent on resync
type string channel_entries.created, .updated, or .deleted
object object Full entry body; absent on removed
changeset object Changed fields only, when the server sends one
occurred_at string ISO8601 timestamp
revision number Monotonic revision for the entry

Delivery is at-least-once and the socket reconnects on its own. A resync event means the stream may have missed changes: watch does not re-read the channel for you, it prints the channels entries list command to run. A resync also does not satisfy --once.

Exit codes: 0 for Ctrl-C, --for expiry and --once; 2 when the query is rejected (locally or by the server); 1 when the connection cannot be re-established within the retry budget. Grant failures map onto the usual auth codes (3 unauthorized, 4 forbidden/realtime disabled, 7 rate limited).

Authentication

nimbu auth login opens your browser on the Nimbu login and consent page (OAuth 2.0 with PKCE and a one-time callback on 127.0.0.1). Your password and two-factor code stay in the browser; the CLI never sees them. The URL is also printed, so you can open it by hand when no browser starts.

nimbu auth login                          # Browser login (default)
nimbu auth login --device                 # One-time code for SSH, containers, remote machines
nimbu auth login --scopes read_channels   # Ask for fewer permissions than the default
nimbu auth status                         # Who you are, how you logged in, token expiry, scopes
nimbu auth token                          # Print a current access token for scripts
nimbu auth logout                         # Revoke the session on the server and forget it locally
  • Device login prints a verification page and a code such as BCDF-GHJK. Open the page on any device, enter the code and confirm with your password or two-factor code. Only enter codes for logins you started yourself; Nimbu never asks you for one. Over SSH, or on Linux without a display, nimbu auth login picks device login automatically.
  • Sessions use a 30-minute access token and a rotating refresh token, both in the OS keychain. The CLI refreshes them on its own, also when several nimbu processes run at once. A session lasts at most 90 days, and ends early when you log out, change your password, or revoke it under your account's CLI sessions in the Nimbu admin. After that, commands fail with auth.not_logged_in (exit code 3); run nimbu auth login again.
  • CI and scripts should keep using an API token: NIMBU_TOKEN=... nimbu ..., or nimbu auth login --token - < token-file to store one (--token - reads stdin, so the token stays out of shell history). These tokens are never refreshed.
  • Password login (nimbu auth login --email you@example.com) still works for now, but is deprecated and prints a warning.
macOS Keychain
  • After an upgrade macOS asks once whether nimbu may use its Keychain item. Release builds are not code-signed, so macOS treats every new version as a different app. Click Always Allow and it stays quiet until the next upgrade. To skip the Keychain altogether, store credentials in an encrypted file: nimbu auth keyring set --backend file (or NIMBU_KEYRING_BACKEND=file). The file backend asks for its password on a terminal, or reads it from NIMBU_KEYRING_PASSWORD.
  • No default keychain (a scratch HOME, some CI images): commands that need credentials fail with auth.keyring_unavailable (exit code 3) instead of waiting on a macOS dialog. Use the file backend or NIMBU_TOKEN there. auth login checks this before it opens the browser.

Configuration

Site Selection

Every command that needs a site picks it in this order, first match wins:

  1. --env <name>: a named environment from nimbu.yml (sets site and API URL together)
  2. --site <site> or NIMBU_SITE
  3. site: in the nearest nimbu.yml
  4. default_site in the global config file

Inside a project, nimbu.yml beats the global default_site, so a write never lands on a site you made the default for another project. nimbu sites current prints the site and which source chose it.

Environment Variables
NIMBU_SITE           # Default site ID
NIMBU_TOKEN          # API token (overrides the keychain; never refreshed)
NIMBU_KEYRING_BACKEND  # auto | keychain | file | secret-service | kwallet | wincred
NIMBU_KEYRING_PASSWORD # Password for the file backend when there is no terminal
NIMBU_API_URL        # API endpoint (default: https://api.nimbu.io)
NIMBU_JSON           # Default JSON output (1/true)
NIMBU_PLAIN          # Default TSV output (1/true)
NIMBU_NO_INPUT       # Disable prompts for CI
NIMBU_READONLY       # Disable write operations
NIMBU_ENABLE_COMMANDS # Command allowlist (comma-separated)
NIMBU_COLOR          # auto | always | never
NIMBU_ASCII          # 1: ASCII symbols only

An empty variable (NIMBU_JSON=) counts as unset.

Config File

~/.config/nimbu/config.json:

{
  default_site: "my-site",
  api_url: "https://api.nimbu.io",
  timeout: "30s",
}
Project File

nimbu.yml in your project directory:

site: my-site
theme: default
apps:
  - id: storefront
    name: storefront
    dir: code
    glob: "**/*.js"
    host: api.nimbu.io
    site: my-site
dev:
  proxy:
    host: 127.0.0.1
    port: 4568
    template_root: .
    watch: true
    watch_scan_interval: 3s
    max_body_mb: 64
  server:
    command: pnpm
    args:
      - vite
      - --port
      - "5173"
    cwd: .
    ready_url: http://127.0.0.1:5173
  routes:
    include:
      - POST /.well-known/*
sync:
  build:
    command: pnpm
    args:
      - build
  roots:
    assets:
      - images
      - fonts
      - javascripts
      - stylesheets
    layouts:
      - layouts
    templates:
      - templates
    snippets:
      - snippets
  generated:
    - javascripts/**
    - stylesheets/**
    - snippets/webpack_*.liquid
Local Server Command

nimbu server starts:

  1. Nimbu simulator proxy (default http://127.0.0.1:4568)
  2. Child dev server command from nimbu.yml

Runtime notes:

  • In a terminal (stdout and stderr both TTYs, no --json/--plain/ --events-json), each child output line is prefixed with a dim dev │ and kept whole next to nimbu's own lines. The child gets FORCE_COLOR set to match nimbu's color decision so its colors survive, unless you set FORCE_COLOR or NO_COLOR yourself. Piped, or with --json, --plain or --events-json, the child inherits nimbu's stdout/stderr and its output passes through unchanged.
  • In a terminal, keys act on the running server: o/l/b open the dev server, live site or admin, Enter prints a blank line to mark a spot in the log, ? or h shows the keys, q quits. Ctrl-C, SIGTERM or q stops gracefully and exits 0; a second Ctrl-C force-stops the child and exits 130. Ctrl-\ force-stops at once and exits 131. Ctrl-Z suspends nimbu; after bg the proxy keeps serving and fg brings the keys back.
  • When stdout is piped and the reader goes away (nimbu server | head), nimbu stops the child and exits instead of dying on SIGPIPE.
  • The child runs in its own process group, so nimbu can stop everything it started. Stop nimbu with SIGINT, SIGTERM or SIGHUP and it takes the child down with it. A SIGKILL (timeout -s KILL, a hard CI cancel) cannot be caught: on Linux the kernel then sends the child SIGTERM, but on macOS the child keeps running. Clean it up with kill -- -<child pid>.
  • Proxy request lines are on by default: 2026-03-04T13:06:32.802Z GET / (200)
  • Use --quiet-requests to hide request lines.
  • Child should proxy simulator requests to NIMBU_PROXY_URL.
  • Vite starters may still accept VITE_NIMBU_PROXY_URL as a compatibility fallback, but NIMBU_PROXY_URL is the preferred name.
  • nimbu server passes NIMBU_DEV_PROXY_TOKEN to the child dev server. Tools such as Vite can use it with NIMBU_PROXY_URL to register in-memory template overlays at PUT /__nimbu/dev/templates/overlays and clear them with DELETE /__nimbu/dev/templates/overlays.
  • Template overlays are local-only, are never written to disk, and override disk templates with the same type/path while the dev proxy is running.
  • ?preview=<token> already works through the simulator when the browser URL carries it. nimbu server --draft <page> (repeatable) also lets you browse the normal page URL: on startup the proxy requests a preview token for each listed page and injects preview=<token> into matching requests (the page fullpath, locale prefixes such as /en/…, and translations.*.fullpath). Child paths are not matched. Startup fails clearly if page drafts are disabled or the page has no draft.

Override example:

nimbu server --cmd pnpm --arg vite --arg=--port --arg 5173 --ready-url http://127.0.0.1:5173

Use --arg=<value> for child arguments that start with -.

Dev Server Integration

Any dev server can integrate with nimbu server by reading the runtime environment that the CLI injects into the child process:

NIMBU_PROXY_URL        # Full simulator proxy URL, for example http://127.0.0.1:4568
NIMBU_PROXY_HOST       # Proxy host
NIMBU_PROXY_PORT       # Proxy port
NIMBU_DEV_PROXY_TOKEN  # Per-process token for local dev proxy APIs
NIMBU_SITE             # Current site ID, when available

The generated values override matching keys from dev.server.env, so project config cannot accidentally point the child at an old proxy or stale token.

Dev server responsibilities:

  • Serve assets and HMR from the child dev server as usual.
  • Proxy page and form requests that should render through Nimbu to NIMBU_PROXY_URL.
  • Use NIMBU_DEV_PROXY_TOKEN only for local calls to NIMBU_PROXY_URL; do not expose it to browser code.
  • Fail startup or show a clear terminal error if required overlay registration fails while NIMBU_DEV_PROXY_TOKEN is present.

Template overlays let a dev server provide virtual Liquid files without writing generated snippets to disk. Register the complete current overlay set with:

PUT /__nimbu/dev/templates/overlays
X-Nimbu-Dev-Token: <NIMBU_DEV_PROXY_TOKEN>
Content-Type: application/json

{
  "templates": [
    {
      "type": "snippets",
      "path": "bundle_app.liquid",
      "content": "{% assign app_bundle_build_timestamp = \"dev\" %}\n"
    }
  ]
}

The type must be layouts, templates, or snippets; path is relative to that type and must end in .liquid or .liquid.haml. PUT replaces the full overlay set, so send every active virtual template each time. Send {"templates":[]} or call DELETE /__nimbu/dev/templates/overlays to clear overlays.

Theme Push/Sync Commands

nimbu themes push uploads managed local theme resources without deleting remote files.

nimbu themes sync uploads managed local theme resources and can also delete managed remote resources that no longer exist locally.

nimbu themes cdn-root prints the resolved CDN root for the configured theme.

Supported managed resource kinds:

  • layouts/**
  • templates/**
  • snippets/**
  • asset roots such as images/**, fonts/**, javascripts/**, stylesheets/**

Notes:

  • code/** and content/** are intentionally excluded from builtin theme sync.
  • --build runs sync.build from nimbu.yml before collecting files.
  • --all uploads the full managed file set.
  • --only narrows uploads to specific managed files, directories, or globs and can be repeated; commas split multiple selectors.
  • --liquid-only, --css-only, --js-only, --images-only, and --fonts-only select managed resource categories before upload/sync.
  • --no-images excludes managed image assets from upload/sync.
  • Explicit selectors and category flags are additive.
  • --prune is only available on themes sync and deletes managed remote extras.

Examples:

nimbu themes push --build
nimbu themes push --liquid-only
nimbu themes push --only javascript/*.js --only stylesheets/*.css --only layouts/default.liquid --only snippets/bundle_app.liquid
nimbu themes sync --only javascript/*.js,stylesheets/*.css
nimbu themes push --only snippets/header.liquid --only stylesheets/theme.css
nimbu themes push --js-only --css-only --only layouts/default.liquid --only snippets/bundle_app.liquid
nimbu themes push --all --theme storefront
nimbu themes push --all --no-images
nimbu themes pull --theme storefront
nimbu themes diff --theme storefront
nimbu themes diff --content
nimbu themes cdn-root
nimbu themes copy --from source-site/storefront --to target-site/storefront
nimbu themes sync --build
nimbu themes sync --all --prune --dry-run
Mail Template Sync

nimbu notifications pull and nimbu notifications push sync notification templates between Nimbu and the legacy on-disk mail contract. nimbu mails is a parity alias with the same pull and push subcommands.

Disk layout:

  • content/notifications/<slug>.txt
  • content/notifications/<slug>.html
  • content/notifications/<locale>/<slug>.txt
  • content/notifications/<locale>/<slug>.html

Text templates use YAML front matter:

---
name: Order created
description: Sent after order creation
subject: Your order was created
---

Plain text body

Examples:

nimbu notifications pull
nimbu notifications push --only order_created
nimbu mails pull --only welcome
Cloud Code App Workflows

nimbu apps config writes a host/site-scoped app entry to nimbu.yml.

nimbu apps push pushes local cloud code files for the selected configured app, preserving dependency order for require() and static ESM imports. --sync also deletes remote files that no longer exist locally. --only can be repeated and each value may be comma-separated.

nimbu apps code pull pulls remote cloud code files for the selected configured app into its local dir. --only accepts either remote names (main.js) or project-relative paths (code/main.js). Pull overwrites selected local files but does not delete local-only files.

nimbu apps logs --app <app> reads cloud code logs for a local app name or app key. Use --tail to print the last --limit entries oldest-first and keep polling for new log entries until Ctrl-C or SIGTERM, which ends the tail with exit 0.

nimbu sites copy copies cloud code by default after content, theme, notifications, redirects, and translations. Pass --skip-cloud-code to leave target app code untouched.

Localized blog/post documents and product fields are preserved during copy. Blog and product copy require explicit default locales on both sites, promote the target default from the matching source locale, and preserve the remaining shared translations. Site copy copies pages before consent so page-backed privacy policies can be remapped safely. With --allow-errors, consent is skipped with a warning when its page dependency was skipped. Shipping rates are inspected but not copied: their region_id values are site-specific, so non-empty source rates produce a warning instead.

Examples:

nimbu apps config
nimbu apps code pull --app storefront
nimbu apps code pull --app storefront --only main.js,code/jobs/daily.js
nimbu apps push --app storefront
nimbu apps push --app storefront --only code/main.js,code/hooks.js --only code/jobs/*.js
nimbu apps push --app storefront --sync --force
nimbu apps logs --app storefront --level error --query checkout
nimbu apps logs --app storefront --tail
nimbu apps logs --app storefront --job sync_products --level error

Development

make build    # Build binary
make fmt      # Format code
make lint     # Run linter
make test     # Run tests
make ci       # Full CI check

On macOS every rebuilt binary is a new app to the Keychain, so a build that reads your stored login asks for access again. Either click Always Allow each time, point dev runs at the file backend (NIMBU_KEYRING_BACKEND=file), or sign each build with the same identity so macOS recognizes it: codesign -f -s "<your signing identity>" -i io.nimbu.cli bin/nimbu. Any code-signing certificate works, including a self-signed one from Keychain Access (Certificate Assistant, Create a Certificate, type Code Signing). When you test with a scratch HOME, also set NIMBU_KEYRING_BACKEND=file and NIMBU_KEYRING_PASSWORD; otherwise commands fail with auth.keyring_unavailable.

License

MIT

Directories

Path Synopsis
cmd
nimbu-cli command
internal
api
channeltypes
Package channeltypes renders Nimbu channel schemas as TypeScript types that plug into the nimbu-js-sdk typings.
Package channeltypes renders Nimbu channel schemas as TypeScript types that plug into the nimbu-js-sdk typings.
cmd
envutil
Package envutil reads process environment conventions shared across nimbu.
Package envutil reads process environment conventions shared across nimbu.
oauth
Package oauth implements the Nimbu CLI's OAuth 2.0 client: authorization server discovery, the loopback (PKCE) and device login flows, token revocation, and a persistent token source that refreshes the stored session.
Package oauth implements the Nimbu CLI's OAuth 2.0 client: authorization server discovery, the loopback (PKCE) and device login flows, token revocation, and a persistent token source that refreshes the stored session.
realtime
Package realtime implements the Nimbu realtime protocol: grant-authenticated ActionCable sessions, live-query validation and a reconnecting watch loop.
Package realtime implements the Nimbu realtime protocol: grant-authenticated ActionCable sessions, live-query validation and a reconnecting watch loop.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL