wats.sh
Guides

CLI Onboarding Guide

Credential-free local onboarding and inspection with the wats CLI.

active · reviewed 2026-05-25

The wats CLI is the package-manager entry point for local onboarding: config/env scaffolding, a setup wizard, offline diagnostics, OpenAPI export, and a local serve wrapper. It is credential-safe by default — no live Meta calls, no token validation, no implicit env-file reads, no file overwrites.

Current commands

wats --help
wats --version
wats init --help
wats setup --help
wats setup ./my-bot --profile test
wats config validate <path>
wats config validate --config <path>
wats doctor --config <path>
wats doctor --config <path> --profile <name> --check-env
wats doctor --config <path> --format json
wats doctor --help
wats upgrade --dry-run
wats upgrade
wats update --dry-run
wats openapi --config <path>
wats openapi --config <path> --profile <name>
wats openapi --config <path> --server-url https://service.example
wats openapi --config <path> --out openapi.json
wats openapi --help
wats serve --config <path> --dry-run
wats serve --config <path> --dry-run --print-routes
wats serve --help
wats messages list --config <path> --env-file .env.local
wats messages show <message-id> --config <path> --env-file .env.local
wats onboarding --public-url <https URL>
wats onboarding --public-url <https URL> --webhook-path /webhooks/whatsapp
wats webhook token
wats webhook token --help

The CLI does not:

  • read or resolve live credentials from existing env files implicitly
  • call Meta Graph APIs
  • validate tokens against Meta
  • manage multiple credential profiles interactively
  • overwrite output files

First-run flow

wats init writes wats.config.yaml and .env.example only — it does not create a package.json or tsconfig.json. From an empty directory, bootstrap a TypeScript project and add the WATS runtime packages first, then run the CLI onboarding commands:

mkdir ./my-bot && cd ./my-bot
bun init -y
bun add @wats/core @wats/graph @wats/http
bun add @wats/config @wats/service
wats init --dry-run
wats init --format yaml --profile local
wats config validate --config wats.config.yaml
wats --version
wats doctor --config wats.config.yaml --check-env
wats serve --config wats.config.yaml --dry-run
  1. Create a project directory and a TypeScript baseline with bun init -y.
  2. Add the WATS runtime packages (@wats/core, @wats/graph, @wats/http, @wats/config, @wats/service).
  3. Preview generated files with wats init --dry-run.
  4. Generate config/env placeholder files with wats init.
  5. Validate the generated config through @wats/config.
  6. Run doctor offline diagnostics.
  7. Start a local dry-run service wrapper around @wats/service.

There is no wats init --yes flag. Non-interactive setup passes the target directory, format, and profile explicitly as shown above. wats setup is an interactive wizard that needs a terminal (TTY stdin); in non-interactive shells use wats init and edit .env.local instead.

Env placeholder policy

wats init generates env-secret references in config, not raw secrets:

auth:
  accessToken:
    env: WATS_ACCESS_TOKEN
webhook:
  verifyToken:
    env: WATS_VERIFY_TOKEN
  appSecret:
    env: WATS_APP_SECRET
service:
  bearerToken:
    env: WATS_SERVICE_TOKEN

Checked-in examples live at examples/config/wats.config.example.yaml, examples/config/wats.config.example.json, and .env.example. They contain placeholder env names only and both config examples parse through @wats/config.

wats init --dry-run
wats init ./my-bot --format yaml --profile local
wats init ./my-bot --format=json --profile prod

wats init writes wats.config.yaml or wats.config.json plus .env.example, refuses to overwrite either file, prints only a redacted count summary, and keeps .env.example secret-bearing values blank. Copy .env.example to an ignored local file such as .env.local before filling real values, or use wats setup for a guided local-only file write.

wats setup [dir] [--profile <name>] prompts for one profile's Graph defaults, WABA id, phone-number id, access token, app secret, webhook path, and local service defaults. Secret prompts display an Input hidden hint before reading, so pasted tokens and app secrets do not echo. It writes wats.config.yaml with env-secret references and .env.local with local values, validates the generated config, refuses to overwrite either target, and rolls back the config if .env.local cannot be created. Blank verify/service-token answers generate local random wats_wh_... and wats_srv_... values. Success output is only:

setup complete
files: 2
profile: [REDACTED_PROFILE]

The setup wizard does not read existing .env.local, resolve env-secret values, validate tokens against Meta, call Meta Graph APIs, manage multiple profiles, or start the service. Prompt answers are validated for empty/whitespace/control-character values, numeric bounds, safe path shape, and safe profile identifiers before any file is written.

Do not pass raw secrets as CLI arguments. Do not commit access tokens, app secrets, webhook verify tokens, service bearer tokens, or WABA/phone-number ids from real accounts.

Validate a config file

wats config validate wats.config.json
# or
wats config validate --config wats.config.yaml

Validation uses @wats/config. On success it prints only a safe count summary:

config valid
default profile: [REDACTED_PROFILE]
profiles: 1

The summary intentionally does not print profile names, env-secret reference names, or secret values. On failure, the command exits 1 and prints a compact ConfigValidationError code/path/message without stack traces or attacker-supplied file-path echoes.

Doctor offline diagnostics

wats doctor --config wats.config.yaml --profile local
wats doctor --config wats.config.yaml --profile local --check-env
wats doctor --config wats.config.yaml --format json

wats doctor runs offline diagnostics for local operator readiness:

  • runtime compatibility
  • package imports
  • package-version drift from package.json for public @wats/* dependencies
  • config discovery and validation
  • selected profile existence
  • route collision checks
  • local OpenAPI generation
  • env variable presence, only with --check-env

The text output is status-only and redacted:

doctor ok
runtime: ok
package-imports: ok
packages: ok
config: ok
profile: ok
routes: ok
openapi: ok
summary: ok=7 warning=0 error=0

--format json returns { ok, summary, checks } with stable check names. The packages check reads package.json only and warns when a listed WATS dependency appears older than the installed CLI; it does not call npm. --check-env reports counts only (missing 1 required env value), never env names or values. Doctor never calls Meta Graph APIs and never writes files.

Check and upgrade WATS package versions

wats --version
wats upgrade --dry-run
wats upgrade
# alias
wats update

wats --version prints the installed @wats/cli version. wats upgrade runs Bun's package updater for the public WATS runtime package set:

bun update --latest @wats/cli @wats/core @wats/graph @wats/http @wats/config @wats/service

Use --dry-run first to see the command without touching package.json or bun.lock. The command reads only the current project's package.json before running Bun and does not read .env.local or call Meta Graph APIs.

Export service OpenAPI

wats openapi --config wats.config.json

This prints OpenAPI 3.1 JSON for the WATS standalone service API implemented by @wats/service.

wats openapi --config wats.config.json --profile prod
wats openapi --config wats.config.json --server-url https://service.example
wats openapi --config wats.config.json --out openapi.json

--out writes only when explicit and refuses to overwrite existing files. Use stdout redirection if you want shell-managed overwrite semantics.

The exported document is not a Meta Graph API OpenAPI document. It describes only WATS service routes: health/readiness, configured webhook ingress, message service routes, and /openapi.json.

Local verify-token generation

wats webhook token

Prints a single random token prefixed with wats_wh_. Copy it into an environment file manually if needed:

WATS_VERIFY_TOKEN=<generated-token>

Do not commit the generated token.

Serve local flow

Dry-run mode is the safest first check:

wats serve --config wats.config.yaml --profile local --dry-run
wats serve --config wats.config.yaml --dry-run --print-routes

Dry-run loads config, starts the @wats/service process wrapper with synthetic in-memory secrets, exposes health/readiness/OpenAPI/webhook routes, and makes no live Meta calls.

For live webhook testing against Meta — public HTTPS tunnel, --live --yes-live --env-file, and the webhook onboarding checklist — see Live webhook.

Troubleshooting

SymptomLikely causeNext step
config_not_foundno config discoveredpass --config wats.config.yaml or run wats init --dry-run
profile_not_foundselected profile missingcheck --profile, WATS_PROFILE, and defaultProfile
missing_secret_envlive mode requested without a required env valuekeep secrets in .env.local or the process environment and pass --env-file .env.local explicitly
output_existsgenerated file already existsinspect the file; the CLI never overwrites
port_in_useservice bind port already takenchoose another --port or stop the existing process
live_confirmation_requiredlive check/service requested without acknowledgementrerun only after reviewing side effects and adding --yes-live

Safety defaults

  • no raw secrets in command arguments
  • no live Meta calls without explicit opt-in
  • no secret values printed
  • no env-secret reference names printed in validation or OpenAPI output
  • no output-file overwrites
  • generated files prefer env references over embedded secrets

On this page