GenieOSdocs

Genie CLI

genie — terminal access to your workspace. Templates, pages, social, marketing, lists, webhooks, logs.

The genie CLI gives you a terminal to a GenieOS workspace. It covers the same public API surface the SDKs use, so CI, local dev loops, and ad-hoc scripts can do the same things.

Install

npm install -g @genie-os/cli
pnpm add -g @genie-os/cli
yarn global add @genie-os/cli
bun install -g @genie-os/cli
npx -y @genie-os/cli help
# pnpm dlx @genie-os/cli help
# bunx @genie-os/cli help

The CLI binary is genie. Verify:

genie --version
# 0.1.0

No system dependencies, runs on Node 18+. The published package is fully self-contained (the SDK is bundled in), so global installs add zero runtime dependencies.

Authenticate

genie login

Opens a TTY prompt: create a key at app.genieos.pro → Settings → API keys, paste the gos_live_* (or gos_test_*) value, and the CLI writes it to ~/.genieos/credentials.json (mode 600). That key is shared by every subsequent command.

For headless contexts (CI), set GENIEOS_API_KEY and skip login:

export GENIEOS_API_KEY=gos_live_...
genie whoami

whoami prints the workspace name, plan, scopes, and rate limit.

Shape of the surface

Every subcommand follows the same noun-verb form, mirroring the REST API and the SDKs:

genie whoami
genie keys       list | get
genie templates  list | get | render | send | create | compose
genie sequences  list | get | enroll
genie events     emit
genie webhooks   list | create | delete
genie pages      list | get | publish | unpublish
genie brand      list | get
genie sms        kit | catalog | send | deliveries
genie social     networks | posts | get | create | schedule | publish | delete
genie marketing  strategy | icps | defaults | set-defaults
genie creations  list | get | spawn | approve
genie lists      list | get | create | add-members
genie links      list | get | utm-suggestions | create | update | analytics
genie qr         create | render
genie logs       tail
# See what the workspace has used before (same as Links designer autocomplete)
genie links utm-suggestions
genie links utm-suggestions --field=source

genie links list --limit=20

genie links create --url=https://acme.com/sale \
  --slug=summer \
  --label="Summer sale" \
  --utm-source=newsletter \
  --utm-medium=email \
  --utm-campaign=summer-2026 \
  --utm-content=hero-cta \
  --tags=launch,q3

genie links get lnk_…
genie links update lnk_… --destination-url=https://acme.com/sale-v2
genie links analytics --link-id=lnk_… --days=30

# QR design → print PDF into Assets
genie qr create --link-id=lnk_… --label="Flyer QR"
genie qr render qr_… --format=pdf --save-to-assets

UTMs are stamped onto the redirect destination at click time. Scope: links:read for list / get / suggestions / analytics / render; links:write for create / update / QR create and render-with-save (create link = 1 credit; QR create = 1 credit; print PDF = 5 credits).

--help is wired up at every level:

genie webhooks --help
genie social --help

Common workflows

Compose a draft from a brief

genie templates compose "Welcome email for new Glow subscribers — warm, one CTA" \
  --key=welcome-glow \
  --name="Welcome · Glow"

Charges compose-template credits; returns id / key / subject. Open the draft in the designer to refine, then send:

genie templates send welcome-glow \
  --to=ada@example.com \
  --vars='{"first_name":"Ada"}'

Create a blank draft

genie templates create --name="Untitled promo" --key=promo-blank

Same seed as New email in the SPA. Prefer compose when you have a brief.

Send a one-off transactional

Useful for support flows / smoke tests:

genie templates send welcome \
  --to=ada@example.com \
  --vars='{"first_name":"Ada","plan":"pro"}'

Tail the audit log

genie logs tail
genie logs tail --interval=5000 --limit=50

Each line is a JSON object — actor, action, target, request id, ts.

CI usage

Every command exits non-zero on failure and writes errors to stderr in the standard envelope:

// stderr
{
  "error": {
    "type": "validation_error",
    "code": "schema_contract_violation",
    "fields": [...]
  }
}

So CI scripts can pipe stderr through jq for fine-grained matching.

A typical "compose a draft in CI" GitHub Action:

name: Compose welcome draft
on:
  workflow_dispatch:
    inputs:
      brief:
        description: Email brief
        required: true

jobs:
  compose:
    runs-on: ubuntu-latest
    steps:
      - run: npm install -g @genie-os/cli@latest
      - env:
          GENIEOS_API_KEY: ${{ secrets.GENIEOS_API_KEY }}
        run: |
          genie templates compose "${{ inputs.brief }}" \
            --key=welcome-ci \
            --name="Welcome (CI)"

Configuration

~/.genieos/config.json (created on first login):

{
  "default_workspace": "ws_01JABC...",
  "base_url": "https://api.genieos.pro",
  "credentials_path": "~/.genieos/credentials.json"
}

Override per-command with flags or env:

genie --workspace ws_OTHER ... # one-off
GENIEOS_BASE_URL=https://staging.api.genieos.pro genie ...  # staging

Logout

genie logout

Removes the credentials file. Does not revoke the key on the server — revoke from app.genieos.pro → Settings → API keys.

The CLI is the SDK

Internally, the CLI is a thin wrapper around @genie-os/sdk. Anything the CLI can do, your Node code can — the CLI just wires up TTY, credentials, and pretty output. If you find yourself shelling out from Node, import the SDK instead.

On this page