@gravity-rail/cli
v0.21.0
Published
Gravity Rail CLI — manage workspaces from the command line
Maintainers
Readme
@gravity-rail/cli
Command-line interface for Gravity Rail — manage workspaces, AI assistants, conversations, workflows, and 40+ resource types from your terminal.
gr workflows list -w $WORKSPACE
gr chats list -w $WORKSPACE -o json | jq '.[].summary'
gr members create -w $WORKSPACE --data '{"first_name":"Jane","email":"[email protected]"}' --allow-writeInstallation
npm install -g @gravity-rail/cli
# or one-shot
npx @gravity-rail/cli --helpRequires Node.js 18+.
The published gr binary is a single self-contained file. Runtime
dependencies only list packages that exist on the public npm registry
(@gravity-rail/sdk). Build-only monorepo packages such as
@gravity-rail/sandbox (used when compiling code-interpreter bundles) stay in
devDependencies so npx / npm install -g do not try to fetch them.
Authentication
OAuth (interactive)
gr login # opens browser for OAuth authorization
gr whoami # verify your identity
gr logout # clear cached credentialsgr login runs authorization code + PKCE (S256) against Gravity Rail's pinned
first-party OAuth client, discovered from the server's
/.well-known/oauth-authorization-server document. That client is public: it
has no client secret, because a CLI distributed to end users cannot keep one
(RFC 8252 §8.5). PKCE, not a shared secret, is what protects the exchange.
A self-hosted server old enough not to advertise a first-party client falls back
to RFC 7591 dynamic client registration, which does issue this installation its
own confidential client. Both kinds of profile refresh normally; nothing needs to
be re-authorized when a server gains the pinned client, though the next
gr login on that profile will switch to it.
Multi-account profiles
Like aws --profile / gh auth switch, the public CLI supports multiple named accounts:
gr auth login --name work # OAuth into a named profile (also sets it current)
gr auth login --name personal
gr auth list # list profiles for the current --env
gr auth switch --name personal # change the default profile
gr auth logout --name work # clear one profile
# Per-invocation selection (does not change the stored default)
gr whoami --profile work
gr tasks list -w $WORKSPACE --profile personal
export GRAVITY_RAIL_PROFILE=workgr login / gr logout remain as shortcuts for the active profile (or --name).
Local monorepo / QA (Gravity Rail developers): after provisioning, import
dev tokens into the same profile store (this replaces the old --realm flag):
yarn gr auth import-dev # or: python scripts/sync_gr_cli_profiles.py
yarn gr whoami --profile superadmin
yarn gr tasks list --profile acme-demo/acme-admin # auto-sets -wWorkspace/member names use one slash (workspace/member). Provisioning auto-syncs
profiles; re-run import after re-seed. Worktree API ports get isolated credential files.
Credential store (security)
| Item | Detail |
| --------------- | ------------------------------------------------------------------------------------------- |
| Location | ~/.gr-cli/credentials-<env>.json (e.g. credentials-prod.json) |
| Directory mode | 0700 on ~/.gr-cli |
| File mode | 0600 on credential files |
| Contents | OAuth client id (+ secret only for a dynamically registered client) + tokens, and/or token-only dev profiles (+ optional workspace UUID) |
| Not stored | GRAVITY_RAIL_API_KEY (process environment only; always overrides profiles) |
| Name validation | Profiles allow name or workspace/member; env names are allowlisted |
| Logging | CLI code never writes token or secret values to stdout/stderr |
Security review notes: tokens never leave the local filesystem except on HTTPS calls to the Gravity Rail API. There is no cloud sync of CLI credentials. Rotate by gr auth logout --name <profile> (or deleting the store file) and logging in again. Multi-user machines should rely on OS home-directory isolation plus the 0600/0700 modes. Do not commit ~/.gr-cli into repositories or bake it into container images.
Profile resolution order for a command:
GRAVITY_RAIL_API_KEY(if set — skips the store entirely)--profile <name>(or--account)GRAVITY_RAIL_PROFILEcurrentProfilein the credential store (fromgr auth switch/ last login)- Profile name
default
API Key (non-interactive)
export GRAVITY_RAIL_API_KEY=your-api-key
gr tasks list -w $WORKSPACEUseful for scripts, CI/CD, and automation. Create scoped keys from workspace settings or via @gravity-rail/sdk. API keys are not written into the profile store.
Organizations that require SSO
Workspaces in an SSO-enforced organization need an org-scoped session in addition to your account login. When you run a gr command against such a workspace after gr login, the CLI opens your browser once to complete the organization's SSO, captures the resulting session over a local 127.0.0.1 callback, and caches it under your profile — subsequent commands reuse it silently until it expires. No token is ever placed in a URL.
For non-interactive use (CI, scripts, code-interpreter sandboxes, or any machine with no browser), org SSO cannot run; use an API key instead — API keys are exempt from the org SSO gate:
export GRAVITY_RAIL_API_KEY=your-api-key # set GRAVITY_RAIL_NO_BROWSER=1 to force the API-key hintCommand Pattern
gr <domain> <action> [options]
gr <domain> <sub-resource> <action> [options]gr tasks list -w $WORKSPACE # list tasks
gr tasks get -w $WORKSPACE --id 42 # get specific task
gr tasks create -w $WORKSPACE --data '{"name":"New"}' --allow-write # create
gr chats labels list -w $WORKSPACE # sub-resourceEvery domain supports --help:
gr --help # all domains
gr tasks --help # task commands
gr chats labels --help # sub-resource commandsWrite Safety
The CLI is read-only by default. Any command that creates, updates, or deletes data requires the --allow-write flag:
# Reads — no flag needed
gr members list -w $WORKSPACE
gr workflows get -w $WORKSPACE --id 5
# Writes — must opt in
gr members create -w $WORKSPACE --data '{"first_name":"Test"}' --allow-write
gr tasks delete -w $WORKSPACE --id 42 --allow-writeThis prevents accidental mutations when exploring production data or running under automation.
Output Formats
Output adapts to context: human-readable tables in interactive terminals, JSON when piped.
# Table (default in TTY)
gr tasks list -w $WORKSPACE
# JSON (default when piped, or explicit)
gr tasks list -w $WORKSPACE -o json
# JSONL (one object per line — great for streaming)
gr tasks list -w $WORKSPACE -o jsonl
# Pipe to jq
gr members list -w $WORKSPACE -o json | jq '.[].email'--jq — filter without a pipe
--jq runs the filter against the JSON the CLI just produced, so the input is
valid by construction and there is no pipeline to get wrong. It implies
-o json.
gr tasks list -w $WORKSPACE --jq 'length'
gr workflows get -w $WORKSPACE --id 3 --jq '{id, name}'Prefer it over | jq in scripts and agent tooling: a mistake in the filter is
reported as a filter error, rather than surfacing as a parse error about output
you cannot see. It needs the jq binary on PATH.
Chain commands
gr chats list -w $WORKSPACE -o json | jq '.[0].id' | xargs -I{} gr chats get -w $WORKSPACE --id {}
## Data Input
Pass data for create/update operations via inline JSON, file, or stdin:
```sh
# Inline JSON
gr tasks create -w $WORKSPACE --data '{"name":"My Task"}' --allow-write
# From file
gr workflows create -w $WORKSPACE --file workflow.json --allow-write
# From stdin (pipe)
echo '{"name":"Piped Task"}' | gr tasks create -w $WORKSPACE --allow-write
cat member.json | gr members update -w $WORKSPACE --id 42 --allow-writeGlobal Options
| Flag | Description |
| --------------------------- | -------------------------------------------- |
| --api-url <url> | Override API base URL |
| --profile <name> | Named account profile (alias: --account) |
| -w, --workspace <uuid> | Workspace UUID (required for most commands) |
| -o, --output-format <fmt> | Output format: json, jsonl, table |
| -v, --verbose | Verbose output to stderr |
| --allow-write | Required for create/update/delete operations |
| --id <id> | Entity ID (for get/update/delete) |
| -d, --data <json> | Inline JSON payload |
| -f, --file <path> | Read JSON payload from file |
| --help | Show help |
| --version | Show version |
Environment Variables
| Variable | Description |
| --------------------------- | -------------------------------------------------------------- |
| GRAVITY_RAIL_API_KEY | API key for non-interactive authentication |
| GRAVITY_RAIL_PROFILE | Default named credential profile |
| GRAVITY_RAIL_API_URL | Override API base URL (default: https://api.gravityrail.com) |
| GRAVITY_RAIL_FRONTEND_URL | Override frontend URL (default: https://app.gravityrail.com) |
Exit Codes
| Code | Meaning |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | Success |
| 1 | General error |
| 2 | Authentication required (run gr login or set GRAVITY_RAIL_API_KEY) |
| 3 | A human-in-the-loop (HITL) interrupt requires out-of-band action before the operation can continue |
| 4 | No assistant reply — a --message turn made tool calls but returned no final summary, or an inference error occurred with no final message (side effects may have been applied) |
UI choices (present_choice_list / render_ui) are handled in the terminal
when stdin is a TTY: the CLI parses OpenUI markup into an arrow-key picker
(ChoiceButtons) or field-by-field form prompts, then resumes the chat with your
selection. Manager (gr manage) and Concierge (gr concierge chat) share this
path. When stdin is not a TTY, those interrupts resume with an empty value (the
agent handles the missing response). Unsupported HITL types still exit 3 with
the JSON contract below.
When a concierge command run with --message (non-interactive mode) hits a HITL
interrupt the CLI cannot resolve on its own, it exits 3 and writes a single
machine-readable JSON object to stdout:
{
"error": "hitl_interrupt",
"message": "A human-in-the-loop interrupt requires out-of-band action before the operation can continue.",
"resume": { "chatId": 42, "chatUuid": "…", "interruptId": "int-abc" },
"interrupt": { "type": "stripe_checkout", "interrupt_id": "int-abc", "url": "…" }
}The interrupt object is intentionally limited to routing fields (type,
interrupt_id, and a redirect url when present); the raw server payload is
not forwarded, as it may contain sensitive data. Use resume.chatUuid /
resume.interruptId to continue the flow out-of-band (e.g. in the web app).
Commands
Core
| Domain | Description |
| ------------- | -------------------------------------------------------------------------- |
| tasks | Workflow tasks — CRUD, archive, clone |
| chats | Conversations — CRUD, labels, filters, messages, export, summaries |
| members | Contacts and team — CRUD, labels, filters, roles, fields, import/export |
| workspaces | Workspace management — CRUD, features, themes, invitations, export/import |
| workflows | Conversational workflows — CRUD, notes, contributors, templates, analytics |
| agents | Autonomous AI members — CRUD, archive |
| assistants | AI personas — CRUD with model and voice configuration |
| assignments | Task-based conversations — CRUD, messages, tools |
Data & Content
| Domain | Description |
| ---------------- | ----------------------------------------------------------------------- |
| data-types | Schema-driven forms and records — CRUD, field indexing, computed fields |
| files | Documents and folders — CRUD, sharing, semantic search, labels |
| sites | Customer portals — CRUD, pages, menus, web crawling |
| events | Automation rules — triggers, CEL conditions, delayed actions, webhooks |
| calendars | Scheduling — calendars, events, event types, availability, Google sync |
| qualifications | Skills evaluation — requirements, submissions, scoring |
| queries | Unified Query Engine — fields, validate, preview, run, syntax reference |
Global (no workspace)
| Domain | Description |
| ---------------------- | --------------------------------------------------------------- |
| concierge | Global Concierge — interactive chat, PSTN --call, SMS --sms |
| account verify-phone | Verify your account phone (required before concierge outbound) |
Communication
| Domain | Description |
| -------------------- | ------------------------------------------------------------------------- |
| phone-numbers | Voice and SMS phone numbers — provisioning, calls, messages |
| recordings | Call recordings — list chunks, get a presigned download URL, save the WAV |
| inboxes | Email inboxes — threads, attachments, routing |
| operator-groups | Live operator routing — groups, presence, strategies |
| notification-rules | Alert configuration — event-based notifications |
Integrations
| Domain | Description |
| ------------------ | -------------------------------------------------------------------------------------------------- |
| discord-bots | Discord bot management — slash commands, threads, account linking |
| slack-apps | Slack app management — installations, threads, accounts |
| fhir-connections | FHIR healthcare connections — patients, practitioners, encounters |
| patientsync | PatientSync PSTN-transfer connection — configure / status / delete (singleton per workspace) |
| documo-fax | Documo (mFax) inbound fax — install / configure / setup / verify / status / faxes |
| app-connections | Platform app integrations |
| monday | Monday.com — boards, columns, webhooks |
Platform
| Domain | Description |
| ----------------- | --------------------------------------------------------------- |
| api-keys | API key management — workspace and global scopes |
| subscriptions | Subscription management |
| reports | Usage reports — AI tokens, voice minutes, SMS, storage |
| ai-models | Available AI models (no workspace required) |
| features | Feature registry (no workspace required) |
| custom-toolkits | Custom tool definitions |
| mcp-servers | MCP server connections |
| milestones | Goal tracking |
| supervisors | AI supervisor management |
| access-grants | Temporary access delegation |
| pronunciations | Voice pronunciation overrides |
| apps | OAuth app management |
| oauth | OAuth provider connections |
| auth | Two-factor authentication, credentials, and password management |
| org | Organization membership, invitations, and org domains |
Recipes
Export all chats as JSON
gr chats list -w $WORKSPACE -o json > chats.jsonList chat messages with pagination
# Default returns up to 100 messages (API max)
gr chats messages list -w $WORKSPACE --id $CHAT_ID
# Paginate with --limit and --offset
gr chats messages list -w $WORKSPACE --id $CHAT_ID --limit 50 --offset 100Filter chats by type
# Only manager (operator-side) chats, sorted most-recent-first.
gr chats list -w $WORKSPACE --chat-type managerAssign and link a phone number to a workspace
# Assign a phone number from the org pool to a workspace…
gr org phone-numbers assign --org-uuid $ORG_UUID --phone-number-id $PN_ID --workspace-uuid $WORKSPACE --allow-write
# …then enable it (link inboxes, voice config) so it can place/receive calls.
gr phone-numbers link -w $WORKSPACE --id $PN_ID --allow-writeManage pronunciations from the workspace context
# `pronunciations` resolves the org from -w; you don't need --org-uuid.
gr pronunciations list -w $WORKSPACE
gr pronunciations create -w $WORKSPACE --term "GravityRail" --respelling "GRA-vit-ee rail" --allow-writeDownload a call recording
# Get a single short-lived presigned URL for the whole call (assembled once, cached).
gr recordings download-url -w $WORKSPACE --chat-id 42
# Or download the assembled WAV straight to a file.
gr recordings download -w $WORKSPACE --chat-id 42 -o call.wavRegister and verify an org domain
# Register the domain and get the ownership TXT record
gr org domains register --org-uuid $ORG_UUID --domain example.com --allow-write
# Verify ownership TXT
gr org domains verify-ownership --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write
# Email DNS: enable records, then verify SPF/DKIM/DMARC/MX/return-path
gr org domains email enable --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write
gr org domains email verify-mx --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write
# Site DNS: enable custom site routing, then verify CNAME
gr org domains site enable --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write
gr org domains site verify-cname --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-writeFind members by label
gr members list -w $WORKSPACE -o json | jq '[.[] | select(.labels[]?.name == "VIP")]'Bulk create from a file
# members.jsonl — one JSON object per line
cat members.jsonl | while read line; do
echo "$line" | gr members create -w $WORKSPACE --allow-write
donePipe workspace config between environments
gr workspaces get -w $SOURCE_WORKSPACE -o json | \
gr workspaces create --data "$(cat -)" --allow-writeList all workflows with their task counts
gr workflows list -w $WORKSPACE -o json | jq '.[] | {name, task_count: (.tasks | length)}'Check who's live as an operator
gr operator-groups list -w $WORKSPACE -o json | jq '.[].name'Concierge setup over your phone (PSTN / SMS)
Outbound concierge modes ring or text your verified account phone only — no destination argument. Verify once, then initiate:
# One-time: OTP SMS + TCPA notice (writes Account.phone + phone_verified_at)
gr account verify-phone --allow-write
# Optional: pass E.164 inline instead of the prompt
gr account verify-phone --phone +15551234567 --allow-write
# Server originates call; CLI exits when Twilio returns callSid
gr concierge chat --call --allow-write
# Server sends opening SMS; CLI exits when messageSid is returned
gr concierge chat --sms --allow-write--call and --sms are mutually exclusive. They cannot be combined with --voice or --realtime on the same gr concierge chat invocation. (gr concierge --call / --sms are equivalent shorthand.)
If the API returns 412 account_phone_unverified, run gr account verify-phone first.
Related
@gravity-rail/sdk— TypeScript SDK for programmatic access (500+ methods, full type safety)- Developer Docs — Full API reference and guides
License
MIT
