Skip to content

BookingSync/smily-cli

Repository files navigation

smily — BookingSync (Smily) API v3 CLI

smily is an ergonomic, comprehensive command-line client for the BookingSync (Smily) API v3.

It aims to be the tool you reach for when you want to talk to API v3 from a terminal or a script:

  • Per-resource commandssmily rentals list, smily bookings get 42, smily clients create … — for every documented endpoint.
  • A raw escape hatchsmily api get <path> reaches anything the API exposes, with pagination and wrapping out of your way.
  • OAuth built in — client-credentials, refresh, and authorization-code helpers; no need to hand-roll token requests.
  • Config profiles — keep tokens for multiple accounts/environments and switch with --profile.
  • Output that fits your workflow — a readable table on a TTY, clean JSON when piped, plus YAML, CSV and NDJSON.
  • Pagination, filtering, sparse fieldsets, sideloading — first-class flags.

It is built on top of the official bookingsync-api gem, so the HTTP, JSON:API and error-handling behavior matches the reference client.

Installation

gem install smily_cli

Or with Bundler:

gem "smily_cli"

This installs the smily executable.

Setup (first run)

Once the gem is installed, get from zero to your first authenticated call in four steps:

# 1. Confirm the executable is on your PATH
smily version

# 2. Get an API token. For scripts/automation the Client Credentials flow is
#    simplest: you need your application's Client ID and Secret from the
#    BookingSync developer portal (https://www.bookingsync.com/oauth/applications).
#    --save writes the token to the default profile at
#    ~/.config/smily/config.yml (created 0600, since it holds secrets).
smily auth client-credentials --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" --save

# 3. Verify the token actually works (calls /me)
smily auth status --check

# 4. Make your first real call
smily rentals list --limit 5

A Client-Credentials token can read across every account that authorized your app; pin one account with --account-id <ID> per call, or store it once:

smily config set account_id <ID>

Don't want to store anything? Every command also reads --token / SMILY_TOKEN directly, so SMILY_TOKEN=… smily rentals list works with no config file at all. See Authentication for the Authorization-Code (per-user) flow and Configuration & profiles for multiple environments.

Quick start

# 1. Get a token (Client Credentials flow) and save it to the default profile
smily auth client-credentials --client-id "$CLIENT_ID" --client-secret "$CLIENT_SECRET" --save

# 2. Explore
smily resources                 # what can I talk to?
smily rentals list --limit 5    # a readable table
smily bookings list --filter status=booked from=2026-01-01

# 3. Fetch one, as JSON
smily rentals get 42 -o json

# 4. Anything the resource commands don't cover
smily api get rentals/42/bookings --paginate -o ndjson

Authentication

API v3 uses OAuth 2.0 Bearer tokens. A token can be supplied (in order of precedence) by:

  1. --token flag
  2. SMILY_TOKEN (or BOOKINGSYNC_TOKEN) environment variable
  3. the active config profile

Getting a token

# Client Credentials — app-level, public scope, reads across all authorized
# accounts. Great for scripts. (Tokens last ~2h.)
smily auth client-credentials --client-id ID --client-secret SECRET --save

# Authorization Code — per-account access.
smily auth authorize-url --client-id ID --redirect-uri https://app.example.com/cb --scope "rentals_read bookings_read"
# (open the URL, approve, copy the ?code=… back)
smily auth exchange --client-id ID --client-secret SECRET --code CODE --redirect-uri https://app.example.com/cb --save

# Refresh an expiring token
smily auth refresh --client-id ID --client-secret SECRET --refresh-token RT --save

# Check what's configured (optionally validate against /me)
smily auth status --check

--save stores the token (and any refresh token / client credentials) in the active profile so subsequent commands just work.

Scoping a Client-Credentials token to one account

A Client-Credentials token spans every account that authorized your app. Pin a single account with --account-id (sent as the account_id query parameter), via SMILY_ACCOUNT_ID, or by storing it in a profile:

smily rentals list --account-id 12345
smily config set account_id 12345 --profile acme

For Authorization-Code tokens the account is implicit, so this is a no-op.

Configuration & profiles

Configuration lives in ~/.config/smily/config.yml (override with SMILY_CONFIG; honors XDG_CONFIG_HOME). The file is written with 0600 permissions because it holds secrets.

smily config init                         # interactive: store a token + base URL
smily config set token TOKEN --profile prod
smily config set account_id 123 --profile prod
smily config use prod                     # make "prod" the default profile
smily config list                         # profiles + settings (secrets masked)
smily config path                         # where's the file?

Use a specific profile per command with --profile NAME, or set SMILY_PROFILE.

Working with resources

Every registered resource supports the same verbs:

smily <resource> list [options]
smily <resource> get ID [options]
smily <resource> create --data <json>       # not on read-only resources
smily <resource> update ID --data <json>
smily <resource> delete ID [--yes]

Run smily resources for the full catalog, or smily <resource> help.

Listing, filtering, pagination

smily bookings list --filter status=booked --filter from=2026-01-01
smily rentals list --fields id name --limit 10
smily rentals list --all -o ndjson           # every page, one JSON object per line
smily rentals list --page 2 --per-page 50    # a specific page
smily rentals list --include photos          # sideload associations
  • --filter k=v / --query k=v — API query parameters (repeat or space-separate). These are sent verbatim, so a resource's documented filters work directly (--filter status=booked from=2026-01-01), and operator query params are forwarded as-is where the endpoint supports them (--filter 'final_price[gteq]=600'). For the validated operator/range DSL, see MCP mode.
  • --fields a b c — sparse fieldset (also limits table/CSV columns).
  • --include a b — sideload associations (returned under linked).
  • --limit N — stop after N records (follows pages as needed; also sizes the page request to min(N, 100)).
  • --all — fetch every page.
  • --page / --per-page — fetch one specific page.
  • --max-pages N — hard backstop on auto-pagination (default 1000); the CLI warns if it stops early.
  • --retry N — retry 429 Too Many Requests up to N times, waiting for the rate-limit window (Retry-After / X-RateLimit-Reset). Off by default.

Creating & updating

Provide attributes as a JSON object; the { "<resource>": [ … ] } envelope that v3 expects is added for you:

smily rentals create --data '{"name":"Villa Sunset","sleeps":6}'
smily clients create --data @client.json         # from a file
echo '{"sleeps":8}' | smily rentals update 42 --data -   # from stdin

For nested creates (e.g. a booking under a rental), point --path at the parent collection (the envelope key stays the resource's):

smily bookings create --path rentals/42/bookings --data @booking.json

The raw api command

smily api <method> <path> is the universal escape hatch — it can reach any endpoint, with any verb, and sends your body verbatim:

smily api get me
smily api get rentals --query per_page=5
smily api get bookings/123
smily api post rentals --data '{"rentals":[{"name":"X"}]}'
smily api get rentals --paginate -o json     # combine all pages into one array

By default it prints the raw JSON response envelope. With --paginate it follows pagination and prints the combined records.

MCP mode (smily mcp)

Besides the REST API, smily can talk to a BookingSync MCP server (Model Context Protocol) — the same servers an AI agent connects to. This is a separate mode with its own credential (mcp_token) and endpoint (mcp_url, defaulting to <base-url>/mcp).

# One-time setup: store the MCP token (and optionally a non-default endpoint)
smily mcp login "$MCP_TOKEN"
smily mcp login "$MCP_TOKEN" --url https://www.bookingsync.com/mcp --profile prod

# Handshake + capabilities, and discover the tools the server exposes
smily mcp info
smily mcp tools

# Convenience wrappers that mirror the REST commands, over the generic
# list / get / resources / resource_schema tools:
smily mcp resources
smily mcp list rentals --limit 5
smily mcp list bookings --filter status=booked --account-id 42
smily mcp get rentals 42
smily mcp schema bookings

# Low-level escape hatch — call any tool with a raw JSON argument object:
smily mcp call list --args '{"resource":"rentals","limit":5}'
smily mcp call resource_schema --args @args.json

The convenience wrappers resolve the generic tool names for you — they work against a server that exposes list/get/… as well as one that prefixes them (api_v3_list, api_v3_get, …).

Advanced filtering (operators & ranges)

smily mcp list speaks the API v3 filter DSL. A plain attribute=value is an exact match; attribute[op]=value applies an operator; repeating an attribute combines the conditions with AND (e.g. a range). Operators: eq, not_eq, gt, gteq, lt, lteq, in, matches, does_not_match (exactly the set a resource reports via smily mcp schema <resource>).

# final_price > 1000
smily mcp list bookings --filter 'final_price[gt]=1000'

# 600 <= final_price < 800  (compound AND range)
smily mcp list bookings --filter 'final_price[gteq]=600' --filter 'final_price[lt]=800'

# starts on/after a date, and one of several references
smily mcp list bookings --filter 'start_at[gteq]=2026-01-01T00:00:00Z' --filter 'reference[in]=A123,B456'

Numeric and boolean values are coerced automatically; ISO 8601 timestamps are kept as strings. Use smily mcp schema <resource> to see which attributes are filterable and the operators each accepts.

Credentials/endpoint resolve like everything else — --mcp-token / SMILY_MCP_TOKEN / profile mcp_token, and --mcp-url / SMILY_MCP_URL / profile mcp_url. --account-id scopes tool calls to a single account (as the account_id tool argument), which is how a multi-account MCP token is pinned. Output honors -o just like the REST commands.

Transport details: smily speaks the Streamable-HTTP JSON-RPC 2.0 transport, performing the initialize handshake (capturing the Mcp-Session-Id) and accepting either application/json or SSE-framed responses.

Output formats

-o, --output selects the format. The default is a human-readable table on an interactive terminal, and json when the output is piped — so scripts get machine-readable data automatically.

Format Notes
table Aligned grid (collections) or key/value (single).
json Pretty JSON — array for lists, object for get.
yaml YAML.
csv Header + rows; nested values become JSON.
ndjson One compact JSON object per line (a.k.a. jsonl).
smily rentals list -o csv --fields id name status > rentals.csv
smily bookings list --all -o ndjson | jq 'select(.status=="booked")'

Global options

Single-value global flags work before or after the subcommand (smily -o json rentals list and smily rentals list -o json are equivalent). Array-valued flags (--fields, --filter) must follow the subcommand.

Flag Env Meaning
--profile NAME SMILY_PROFILE Config profile to use
--token TOKEN SMILY_TOKEN OAuth access token (REST)
--mcp-token TOKEN SMILY_MCP_TOKEN MCP access token (smily mcp)
--mcp-url URL SMILY_MCP_URL MCP endpoint (default <base-url>/mcp)
--base-url URL SMILY_BASE_URL API base (default https://www.bookingsync.com)
--account-id ID SMILY_ACCOUNT_ID Scope to an account (client-credentials tokens)
--max-pages N SMILY_MAX_PAGES Auto-pagination backstop (default 1000)
--retry N SMILY_RETRY Retry 429s N times, honoring the rate-limit reset
-o, --output SMILY_OUTPUT Output format
--fields a b c Field/column selection
--no-color NO_COLOR Disable ANSI color
-q, --quiet Suppress status messages
-v, --verbose SMILY_VERBOSE Log HTTP traffic to stderr (secrets redacted)

Security notes

  • Config files are written 0600 in a 0700 directory; secrets are masked in smily config list / smily auth status.
  • --verbose scrubs Authorization / client_secret / tokens from the logged traffic, so verbose output is safe to share.
  • The CLI warns (like the GitHub CLI) if a credential would be sent over a non-HTTPS endpoint.

Shell completion

smily completion bash > /usr/local/etc/bash_completion.d/smily
smily completion zsh  > "${fpath[1]}/_smily"
smily completion fish > ~/.config/fish/completions/smily.fish

Exit codes

0 success · 1 generic/API error · 2 usage error · 3 auth error (401/403) · 4 not found (404) · 5 rate limited (429).

Using it as a library

The library layer is usable without Thor:

require "smily_cli"

client = SmilyCli::Client.new(token: ENV["SMILY_TOKEN"])
result = client.list("rentals", limit: 10)
result.records            # => [ { "id" => 1, ... }, ... ]

puts SmilyCli::Formatters.render(result, format: "json")

Development

bin/setup          # install dependencies
bundle exec rake   # run the specs and RuboCop
bundle exec rspec  # just the specs
bin/console        # an IRB session with the gem loaded

CI (GitHub Actions) runs the specs across Ruby 3.2–3.4, RuboCop, and Brakeman.

License

Available as open source under the terms of the MIT License.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors