Crafty Hosting CLI (crafty deploy)

@craftware/crafty-cli deploys a frontend's build output to Crafty Hosting. The flow is: the CLI zips your build, uploads it to your Crafty tenant, and Crafty ships it to the hosting provider on its own centralized account. You never touch the provider directly — you only ever talk to "Crafty Hosting".

When to Use

  • Starting a new site from an official starter: npm create crafty / crafty create.
  • Deploying/shipping/publishing a built frontend (./dist, .output/public, out/, …) to Crafty Hosting.
  • Running any crafty command: login, init, deploy, status, logout.
  • Creating or editing crafty.json.
  • Wiring a custom domain to a deployed site, or debugging a failed deploy.

Scope

  • In scope: using the published @craftware/crafty-cli against a deployed Crafty tenant.
  • Out of scope: authoring content (use the crafty-cms skill / MCP server), building the Crafty backend, or the hosting provider's own dashboard.
  • Related: the same CLI generates TypeScript types for the site's content models — crafty types writes crafty.types.ts from the environment API key (CRAFTY_API_KEY / --api-key, no login needed). Run it before writing code that reads entries or blocks; see the CLI README ("Content types").

New project: npm create crafty

For a new site, skip login + init below: npm create crafty@latest <dir> -- --url=https://<tenant>.crafty.do --yes (same as crafty create) copies an official starter (Astro "sitio de servicios"), logs in via the device flow with the scopes it needs (deploy crafty:read crafty:write crafty:publish), creates the site with the starter's content model and demo content (published unless --draft), and writes .env, crafty.json and src/crafty.types.ts. Then npm install && npm run dev, and npm run deploy (build + crafty deploy). Read the generated project's AGENTS.md before changing it. Flags and details: references/cli-commands.md.

Prerequisites

  1. Node.js 20+ and the CLI installed: npm i -g @craftware/crafty-cli.
  2. A built frontend — run the framework's build first (npm run build) so the output directory exists.
  3. CRAFTY_API_URL present in the project's .env (the same URL the app uses to read the Crafty API). The CLI derives the tenant from it, so there is nothing else to configure.

The flow

crafty login     # OAuth device flow — approve in the browser
crafty init      # writes crafty.json (site, environment, buildDir)
npm run build    # produce the build output (framework-specific)
crafty deploy    # zips the build dir, uploads, prints the live URL
  1. crafty login — prints a URL + short code; the user approves in a browser already signed into Crafty. The token (scope deploy) is saved per tenant host to ~/.crafty/credentials. No tokens are pasted by hand. The CLI keeps one session per tenant, so a user with several tenants stays logged into all of them and never re-logs in when switching projects. Use crafty login --host=<url> to authenticate a tenant without being inside its project.
  2. crafty init — pick the site slug and environment (default production); buildDir defaults to dist. Non-interactive: crafty init --site=<slug> --environment=<env> --build-dir=<dir>.
  3. crafty deploy [dir] — packages the build dir (default from crafty.json), uploads it, polls until deployed/failed, and prints the URL. Override the dir with a positional arg (e.g. crafty deploy .output/public).
  4. crafty status — shows the project's last deployment status/URL.
  5. crafty skills install — copies the Crafty agent skills into .claude/skills/ (--global for ~/.claude/skills/, --dir=<path> for other agents); re-run to update them.
  6. crafty logout — revokes and removes the session for the current project's tenant (other tenants stay signed in).
  7. crafty update (--update / -u) — self-updates the globally installed CLI to the latest version on npm (no-op if already current).

crafty.json

{
  "site": "acme",
  "environment": "production",
  "buildDir": "dist"
}

Set buildDir to your framework's output: Vite/CRA dist, Next static export out, Nuxt .output/public, Astro dist.

How context is resolved (in order)

  1. CRAFTY_API_URL from the project .env (a trailing /api/v1 is stripped) → tenant base URL.
  2. crafty.json → site, environment, buildDir.
  3. CRAFTY_TOKEN from the process environment (CI) or ~/.crafty/credentials → access token.

In CI (GitHub Actions)

There is no crafty login in CI: set CRAFTY_TOKEN (a personal access token with the deploy scope, from Perfil → Tokens in the panel) and CRAFTY_API_URL as environment variables, then run npx --yes @craftware/crafty-cli@latest deploy after the build. Never put CRAFTY_TOKEN in .env (the CLI ignores it there). To redeploy when content changes, point a Crafty content webhook with format "GitHub Actions" at https://api.github.com/repos/{owner}/{repo}/dispatches with an Authorization: Bearer <GitHub token> header, and trigger the workflow with on: repository_dispatch: types: [crafty-content] plus concurrency: { group: crafty-deploy, cancel-in-progress: true }. The CLI README has the full workflow.

Custom domains

A site gets an automatic, tenant-scoped subdomain ({tenant}-{hash}.crafty.run, one per environment, shown in Site → Hosting). To use your own domain, add it in the Crafty panel under Site → Hosting; Crafty returns a CNAME record to set at your DNS provider. The CLI deploys; domains are managed in the panel.

  • Add a subdomain such as www.example.com. Create only the CNAME Crafty shows and leave every other record alone, especially the email ones (MX, and the SPF, DKIM and DMARC TXT records).
  • A root domain (example.com) only connects when its DNS is on Cloudflare. Never put a CNAME on the root: a CNAME cannot coexist with MX or TXT records, so it breaks the domain's email. Redirect the root to www at the DNS provider instead.
  • Crafty never edits the customer's DNS; it only manages records for its own crafty.run subdomains.

Common pitfalls

  • No has iniciado sesión → run crafty login first (in CI, set CRAFTY_TOKEN).
  • No hay crafty.json → run crafty init in the project root.
  • El directorio de build no existe → run the framework build first, or fix buildDir / the positional dir argument.
  • No CRAFTY_API_URL → crafty login/deploy can't find the tenant; add it to .env (or export it).
  • Deploying source instead of the build → point buildDir at the compiled output, not the project root or src/.
  • Each deploy replaces the previous one for that environment — there is no per-branch preview or rollback in the CLI.
  • Unknown URLs of a build without 404.html get the site's index.html with a 200 (single-page app fallback), so client-side routes work. Missing files inside folders that hold no HTML (assets/, _astro/, fonts/…) return a real 404: Crafty Hosting adds a 404.html there at deploy time. Ship your own root 404.html to turn the fallback off. Each deploy also keeps serving the previous deploy's hashed bundles (assets/index-<hash>.js), so a page cached before the deploy still loads.

References

  • references/cli-commands.md — every command, flag, exit behavior, and end-to-end examples per framework.
  • references/install.md — installing the CLI, and adding the Crafty agent skills (crafty skills install, or zips for Claude.ai and Claude Desktop).