slew docs/reference/cli-reference

CLI reference

slew is the command-line tool for slew — an EU-native deploy platform: git in, live URL out.

Installation and requirements

  • Runtime: Node.js >= 20.19.
  • Distribution: an npm package named slew (MIT license). It installs a single binary, slew.
  • Dependencies: deliberately one runtime dependency (tar) — the CLI is small enough to read before you trust it with your deploys.
# after install, verify:
slew --version   # prints the CLI version, e.g. 0.1.0
slew --help      # prints the logo and usage

Running slew with no command prints the help and exits with code 1; slew --help prints the same help and exits 0.

Global options

Flag Effect
--project <name> Override the slew.json project link (used by deploy, deployments, rollback, stats)
--hours <n> Time window for stats (default 24, max 72)
--version Print the CLI version and exit
--help Show the ASCII logo and help text

Flag parsing is strict: an unknown flag is an error.

Commands

Git integration

Command Effect
slew git Connected repository, build recipe, and recent builds
slew git connect <owner/repo> Deploy this project on every push (flags: --branch, --build-cmd, --install-cmd, --output, --no-previews); opens the GitHub App install page when needed and waits
slew git build Queue a build of the branch head now
slew git log [build-id] Print a build's log (default: the latest build); header on stderr, log on stdout
slew git disconnect Stop deploying from git

Organizations

Shared team workspaces — see Organizations for the model and roles.

Command Effect
slew org list Your orgs and your role in each
slew org create <name> Create an org (--display-name <s>); you become its owner
slew org members <org> List members with roles
slew org invite <org> <email> Email an invite (--role member|admin); always prints the accept link, and says so when no email was sent
slew org invites <org> Pending invites
slew org revoke-invite <org> <id> Revoke a pending invite
slew org remove <org> <login> Remove a member (admin+)
slew org leave <org> Leave the org yourself
slew org token <org> <ai|deploy> [label] Mint an org-owned token (admin+); the secret prints once. deploy = CI credential limited to deploying the org's projects, ai = gateway key drawing the org's credit
slew org tokens <org> / slew org revoke-token <org> <id> List / revoke org tokens (admin+)
slew org installations <org> GitHub installations shared with the org
slew org share-github <org> [installation-id] Share one of your GitHub installations with the org (no id = pick from a list)
slew org unshare-github <org> <installation-id> Unshare (the sharer or an admin)

Org tokens exist so nothing a team depends on hinges on one person's account: put a deploy token in CI as SLEW_TOKEN and deploys keep working no matter who leaves. slew whoami makes org-token identity explicit.

slew projects [--org <name>]

List your personal projects — or, with --org, an organization's. Org-owned projects in any listing show (org: <name>).

slew login

Authenticate via the GitHub device flow.

  1. POST /auth/device to the control plane to start the flow.
  2. Prints the one-time user code and verification URL, and opens the URL in your browser (best effort — open on macOS, start on Windows, xdg-open elsewhere; the URL is printed either way).
  3. Polls POST /auth/device/poll on the server-provided interval until the deadline (expires_in). API error codes are handled: authorization_pending keeps polling, slow_down adds 5 seconds to the interval, expired_token and timing out both fail with "The code expired. Run 'slew login' again.", and access_denied fails with "Authorization was denied on GitHub."
  4. On success, writes the token to the credentials file (mode 0600), then calls GET /me to confirm.
$ slew login
First, copy your one-time code: ABCD-1234
Then authorize at: https://github.com/login/device
Waiting for authorization…
Logged in as hessel.

slew logout

Deletes the credentials file. Prints Logged out. if a file was removed, Already logged out. if there was nothing to delete. Note this only removes the stored file — it does not affect a SLEW_TOKEN environment variable.

slew whoami

Calls GET /me with the current token and prints the account as <github_login> (<id>, <email>) — the email part is omitted when the account has no email.

$ slew whoami
hessel (acc_1, hessel@example.com)

slew dashboard

Opens the web console already signed in.

  1. Requires a token (stored or SLEW_TOKEN); fails with "Not logged in" otherwise.
  2. Validates the token via GET /me first, so a dead token doesn't just bounce you to the login form.
  3. Opens <dashboard-url>/#token=<token>. The token rides in the URL fragment, which browsers never send to the server; the dashboard stores it and strips it from the URL on load.
$ slew dashboard
Opening https://app.slew.cloud as hessel…
If no browser opened, run 'slew token cli' and paste it there.

slew init [name]

Create a project (or link an existing one) for the current directory. With --org <name> the project is created in that organization instead of your personal workspace.

  • If the directory is already linked via slew.json and no name is given, it prints Already linked to '<project>'. Pass a name to relink: slew init <name> and stops.
  • Without a name argument, the project name is derived from the directory basename: lowercased, runs of characters outside [a-z0-9-] replaced with -, leading/trailing dashes stripped.
  • The name must match ^[a-z0-9-]{3,40}$ or the command fails.
  • It then POST /projects. If the API returns error code name_taken, the CLI tries GET /projects/<name> — if that succeeds (it's your own project), it links it instead; otherwise it fails with "'<name>' is already taken. Pick another".
  • On success it writes slew.json in the current directory.
$ slew init my-site
Created project 'my-site' → https://my-site.slew.cloud
Linked in slew.json — run 'slew deploy' to ship.

Or, when the name exists and belongs to you:

Linked existing project 'my-site' → https://my-site.slew.cloud
Linked in slew.json — run 'slew deploy' to ship.

slew deploy [dir]

Deploy a built site directory (default: current directory). See How deploy works for the full pipeline.

$ slew deploy dist
Deploying 42 files (1.3 MB) to 'my-site'…
Deployed dep_9f3
Live at https://my-site.slew.cloud

Accepts --project <name> to override the slew.json link. Fails with Not a directory: <path> if the argument doesn't resolve to a directory.

slew deployments

List the linked (or --project) project's deployments via GET /projects/<name>. Prints No deployments yet — run 'slew deploy'. when empty. Each line shows: an marker if it is the active (live) deployment, the deployment id, status (one of uploading, live, superseded, failed), timestamp (YYYY-MM-DD HH:MM), file count, and size — the latter two omitted when null:

● dep_9f3  live        2026-07-10 09:41  42 files  1.3 MB
  dep_8a1  superseded  2026-07-09 17:02  41 files  1.2 MB

slew deployments rm <deployment-id>

Delete an inactive deployment — its stored files and its record — via DELETE /projects/<name>/deployments/<id>. The active (live) deployment is refused with 409 deployment_live; roll back to another deployment first. Deletion is permanent: a deleted deployment can no longer be rolled back to. remove and delete work as aliases for rm.

$ slew deployments rm dep_8a1
Deleted dep_8a1 from 'my-site'.

slew domains cert <domain>

Check the TLS certificate a custom domain is actually serving, via GET /projects/<name>/domains/<domain>/certificate:

$ slew domains cert www.example.com
www.example.com
  status   valid
  issuer   Let's Encrypt R11
  subject  www.example.com
  expires  2026-08-30 00:00 UTC (50 days)

If the domain is unreachable on :443 (DNS not pointed at the CDN yet), the command says so and suggests re-running slew domains add <domain> to retry issuance.

slew password [status|set <password>|rm]

Gate the whole site behind a shared password — see Password-protected sites. set takes 4–128 characters and calls PUT /projects/<name>/password; rm (DELETE) makes the site public again; bare slew password reports the current state. Changing or removing the password immediately invalidates every visitor's unlock cookie.

$ slew password set "correct horse"
'my-site' is password protected — visitors get an unlock screen on every domain it serves on.

slew rollback <deployment-id>

Make an earlier deployment live again. Requires the deployment id (fails with Which deployment? Usage: slew rollback <deployment-id> (see 'slew deployments') otherwise). Calls POST /projects/<name>/deployments/<id>/activate, then re-fetches the project to print the URL:

$ slew rollback dep_8a1
Rolled 'my-site' to dep_8a1
Live at https://my-site.slew.cloud

slew stats [--hours <n>]

Live traffic for the linked (or --project) project, straight from the CDN's edge logs via GET /projects/<name>/stats — part of slew's cookieless analytics. --hours is 1–72 (default 24):

$ slew stats
my-site — last 24 h (UTC)
  requests   12,405 (96.2% from cache)
  bandwidth  1.9 GB
  errors     23 (4xx 21 · 5xx 2)
  by hour    ▁▁▂▃▅█▆▅▃▂▂▃▄▅▆▅▄▃▂▂▁▁▁▁

  my-site.slew.cloud  12,001  1.8 GB
  www.example.com        404  84 MB

  top paths
      8,911  /
      1,204  /blog/launch

  countries  NL 4,102 · DE 3,377 · FR 1,559

Every hostname the project serves — the slew.cloud subdomain and custom domains — is counted and listed separately. An empty window prints No requests in this window.; a window busier than the aggregation ceiling is marked [sampled] next to the request count. Longer ranges (up to 365 days) and the audience view — pageviews, visitors, referrers — live in the console's Traffic tab and the HTTP API.

slew env [ls | set KEY=value … | rm KEY]

Per-project environment variables, encrypted at rest. Each variable has a scope — where it is injected:

Scope Injected into
runtime (default) The running server container, at start
build Git builds (npm run build and friends)
both Both
slew env set DATABASE_URL=postgres://…        # runtime (default)
slew env set FLAGS_TOKEN=abc --scope build    # git builds only
slew env set API_URL=https://… --scope both
slew env                                      # list; non-runtime scopes shown as [build] / [both]
slew env rm FLAGS_TOKEN

Runtime changes restart the live server on its next request; build changes apply to the next git build. Updating a value without --scope keeps the variable's stored scope. PORT and HOSTNAME are set by the platform.

slew logs [--tail <n>]

Runtime logs of the live server instance (static sites have none). "Scaled to zero" means no instance is running — hit the URL once to wake it.

slew token <ai|cli> [label]

Create a token via POST /tokens. The first argument must be ai (an AI API key) or cli (an automation/deploy token); anything else fails with Usage: slew token <ai|cli> [label]. Remaining arguments are joined into the label; when omitted, the label defaults to CLI-created AI key or CLI-created deploy token.

The token value is printed to stdout and the warning to stderr, so slew token cli "CI deploy" > token.txt captures only the secret:

$ slew token cli "CI deploy"
slew_xxxxxxxxxxxxxxxx
Store this key now; it will not be shown again.

Help and version

slew --help      # logo + usage, environment variables, options
slew --version   # version read from package.json (falls back to 0.0.0)

Configuration

Credentials file

  • Location: $SLEW_CONFIG_DIR/credentials.json if SLEW_CONFIG_DIR is set, otherwise $XDG_CONFIG_HOME/slew/credentials.json, defaulting to ~/.config/slew/credentials.json.
  • Format: {"token": "..."} (JSON, trailing newline).
  • Permissions: the config directory is created with mode 0700 and the file is written and re-chmodded to 0600.
  • slew logout removes this file.

Environment variables

Variable Purpose Default
SLEW_API_URL Control-plane base URL; trailing slashes are stripped https://api.slew.cloud
SLEW_DASHBOARD_URL Web console URL used by slew dashboard; trailing slashes stripped https://app.slew.cloud
SLEW_TOKEN Token override for CI — when set, it takes precedence over the stored credentials file
SLEW_CONFIG_DIR Override the config directory $XDG_CONFIG_HOME/slew or ~/.config/slew
NO_COLOR Disable ANSI colors in the logo/branding output (colors are also disabled when stdout is not a TTY)

Gotcha: because SLEW_TOKEN always wins, a stale SLEW_TOKEN in your shell silently overrides whatever slew login stored.

Written by slew init into the project directory; read by deploy, deployments, and rollback (from the current working directory) unless --project is passed:

{
  "project": "my-site"
}

Only the project string field is read; a file without it is treated as no link. The slew.json file itself is excluded from deploys. When no link exists and no --project flag is given, commands fail with: No linked project here. Run 'slew init' first, or pass --project <name>.

How deploy works under the hood

Builds happen on your machine or in CI — the CLI ships already-built output.

  1. Resolve the project--project flag, else slew.json in the cwd.
  2. Resolve and check the directory — the optional [dir] argument (default .) must exist and be a directory.
  3. List files: recursive directory walk producing sorted, /-separated relative paths.
    • Excluded: any path containing a .git or node_modules directory component (at any depth), and the top-level slew.json link file.
    • Symlinks are never followed — only regular files are included, so a site cannot package files outside its build directory (e.g. ~/.ssh or a CI secret mount).
    • An empty result fails with Nothing to deploy: no files found in <dir>.
  4. Enforce size limits before packing:
    • Any single file over 25 MBFile exceeds the 25 MB limit: <file>.
    • Total uncompressed size over 250 MBSite exceeds the 250 MB uncompressed limit.
  5. Pack the files into an in-memory gzip tarball (tar.create with gzip: true, portable: true, follow: false).
  6. Compressed-size check: the tarball must be at most 100 MB or the deploy fails.
  7. Capture the git SHA (best effort): git rev-parse HEAD in the deploy directory; skipped silently if it isn't a git repo.
  8. Upload: POST /projects/<name>/deployments with the raw tarball body, content-type: application/gzip, an x-slew-cli-version header, and x-slew-git-sha when available.
  9. Activation is server-side: the response ({ deployment_id, url }) means the deployment is live; the CLI prints both. Rollback later re-activates an older deployment via the activate endpoint.

The CLI sends authorization: Bearer <token> on all authenticated calls and surfaces errors from the control plane's { error: { code, message } } wire shape.

Exit codes and error behavior

Situation Exit code Output
Success 0 Command output on stdout
Running slew with no command 1 Help text (with logo)
slew --help / slew --version 0 Help / version
User-facing failure (CliError) 1 slew: <message> on stderr, no stack trace
Control-plane error (ApiError) 1 slew: <message> (<code>) on stderr — e.g. slew: Missing token (unauthorized)
Unexpected error 1 The raw error (including stack trace) on stderr
Unknown command 1 slew: Unknown command '<cmd>'. See 'slew --help'.

Network failures (server unreachable) are surfaced as an ApiError with status 0 and code network_error: Could not reach <base-url> (…). When an error response has no parseable JSON body, the code falls back to unknown and the message to API returned HTTP <status>.