@craftware/crafty-cli
Crafty Hosting CLI — deploy your frontend build to Crafty Hosting with a single command. The CLI talks to your Crafty CMS tenant; the underlying hosting provider is fully abstracted (you never deal with it directly).
Install
npm i -g @craftware/crafty-cli
Requires Node.js 20+.
Start a new site
npm create crafty@latest # or: crafty create [dir]
Copies an official starter (today: Astro, "sitio de servicios"), logs you into
your tenant in the browser (scopes deploy crafty:read crafty:write crafty:publish), creates the site with the starter's block types, collections,
form and demo content (published; --draft keeps it as drafts), and writes
.env, crafty.json and src/crafty.types.ts. Then npm install && npm run dev. Starters live in sdk/starters; npm run templates copies
them into templates/ (done automatically by npm pack / npm publish).
Quick start
From your frontend project (the one that already consumes the Crafty API):
crafty login # opens a browser to approve the CLI (OAuth device flow)
crafty init # writes crafty.json (site + environment + build dir)
npm run build # produce ./dist (framework-specific)
crafty deploy # zips the build, uploads it, prints the live URL
The CLI reads CRAFTY_API_URL from your project's .env to know which tenant to
talk to, so there is nothing else to configure. Hosting must be enabled for your
tenant, site and environment (in the Crafty panel) before a deploy is allowed.
Multiple tenants
The CLI keeps one session per tenant host, so you stay logged into all of
your tenants at once. Each project's CRAFTY_API_URL selects which stored
session is used — switching directories never forces a re-login. Log into a
tenant without entering its project with crafty login --host=<url>.
Commands
| Command | Description |
|---|---|
crafty create [dir] [--url=<url> --template=<id> --name=<name> --site=<slug> --draft --yes] | Create a project from a starter and its site in Crafty (same as npm create crafty). See Start a new site. |
crafty login [--host=<url>] | Authenticate via the OAuth 2.0 Device Authorization Grant. Without --host, the tenant comes from CRAFTY_API_URL. The token (scope deploy) is stored per host in ~/.crafty/credentials (mode 600). |
crafty init [--site=<slug> --environment=<env> --build-dir=<dir>] | Create crafty.json in the project. |
crafty deploy [dir] [--force] | Package and deploy a build directory (default: the buildDir from crafty.json, i.e. dist). The project root, your home directory and directories outside the project are refused unless --force is passed. |
crafty status | Show the status of the project's last deployment. |
crafty types [--api-key=<key> --url=<url> --out=<file>] | Generate TypeScript types for the collections and block types of an environment (default output: crafty.types.ts). See Content types. |
crafty skills list / crafty skills install [name…] [--global | --dir=<path>] | List or install the Crafty agent skills (.claude/skills by default). |
crafty logout | Revoke and remove the session for the current project's tenant. |
crafty update (crafty --update / -u) | Self-update the globally installed CLI to the latest version on npm. |
crafty --version / crafty help | Print version / usage. |
Content types
crafty types writes TypeScript types for the content models (collections and
block types) of one environment, so your code — and your coding agent — stops
guessing field shapes:
crafty types # uses CRAFTY_API_URL + CRAFTY_API_KEY from .env
crafty types --api-key=$STAGING_KEY --out=src/crafty.types.ts
- The environment comes from the API key: types generated with the staging key describe staging. Regenerate after changing a collection or block type.
- It uses the public environment API key (
X-API-Key), not thecrafty loginsession.--url/CRAFTY_API_URLselects the tenant;httpsis required except onlocalhost. - Output:
<Slug>Entryper collection (entry metadata + fields, as returned by/collections/{slug}/entries),<Slug>BlockDataand<Slug>Blockper block type, theCraftyPageBlockunion (discriminated bytype), theCraftyCollectionTypes/CraftyBlockDataTypesslug maps, and the sharedCraftyEntryReference/CraftyMediaSummary. - Field mapping:
string/text/longText/url/datetime→string,number/decimal→number,boolean→boolean,select→ union of its option values (an array of them withmultiple),stringList→string[],repeater→ array of item objects, media references → ids plus a<key>_mediafile summary, collection references →CraftyEntryReferenceon entries and the referenced entry type on blocks,json→unknown. Optional fields are| null.
import type { BlogEntry, CraftyPageBlock } from "./crafty.types";
const { data } = await crafty.entries.list("blog");
const posts = data as unknown as BlogEntry[];
const page = await crafty.pages.get("home");
for (const block of page.blocks as unknown as CraftyPageBlock[]) {
if (block.type === "hero") {
block.data.heading; // typed as HeroBlockData
}
}
(Generic SDK methods such as crafty.entries.list<"blog">() are planned; until
then, cast the SDK responses to the generated types.)
The schema comes from GET /api/v1/schema (JSON Schema, versioned by
format_version); the CLI refuses a format it does not know — run
crafty update.
crafty.json
{
"site": "acme",
"environment": "production",
"buildDir": "dist"
}
Set buildDir to your framework's output: Vite/CRA/Astro dist, Next.js static
export out, Nuxt .output/public, SvelteKit (static adapter) build.
How it resolves context
CRAFTY_API_URLfrom the project.env(or the environment) → tenant base URL.crafty.json→site,environment,buildDir.CRAFTY_TOKENfrom the process environment (CI) or, without it,~/.crafty/credentials→ the access token stored for that tenant host.
Deploy from CI (GitHub Actions)
In CI there is no crafty login. Create a personal access token with the
deploy scope in the Crafty panel (Perfil → Tokens, as a tenant admin) and
pass it as CRAFTY_TOKEN, together with CRAFTY_API_URL. CRAFTY_TOKEN is
only read from the environment, never from .env.
To redeploy when content changes, add a content webhook in Site →
Integraciones → Webhooks with the format GitHub Actions, the URL
https://api.github.com/repos/{owner}/{repo}/dispatches and an Authorization: Bearer <token> header (a fine-grained GitHub token for that repository with
Contents: read and write). Crafty then sends a repository_dispatch with
event_type: crafty-content and the Crafty payload in client_payload; its
test ping uses crafty-ping, which checks the token without starting a build.
# .github/workflows/crafty-deploy.yml
name: Deploy to Crafty Hosting
on:
push:
branches: [main]
repository_dispatch:
types: [crafty-content]
workflow_dispatch:
# Publishing 20 entries in a row sends 20 events: each new run replaces the
# pending one and cancels the one in progress, so only the last one deploys.
concurrency:
group: crafty-deploy
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
env:
CRAFTY_API_URL: ${{ vars.CRAFTY_API_URL }}
CRAFTY_API_KEY: ${{ secrets.CRAFTY_API_KEY }}
- run: npx --yes @craftware/crafty-cli@latest deploy
env:
CRAFTY_API_URL: ${{ vars.CRAFTY_API_URL }}
CRAFTY_TOKEN: ${{ secrets.CRAFTY_TOKEN }}
github.event.client_payload carries the event (event, site,
environment, data.type, data.slug, …) if the build needs to know what
changed.
For safety, the token is only ever sent to the host it was issued for, https is
required (except localhost), and .git, .env*, .envrc, .dev.vars, .npmrc,
.netrc, .aws, .ssh, .crafty, id_rsa*-style keys, *.pem, *.key,
node_modules and symlinks are excluded from the uploaded build (case-insensitive).
Deployed URL & custom domains
A successful deploy is served at a tenant-scoped subdomain
{tenant}-{hash}.crafty.do, one per environment (DNS + TLS set up automatically).
To use your own domain, add it in the Crafty panel under Site → Hosting; it
returns a CNAME record to create at your DNS provider. Use a subdomain such as
www, create only that record and leave the email records (MX, SPF, DKIM, DMARC)
untouched. A root domain only connects when its DNS is on Cloudflare; never put a
CNAME on the root, since it breaks the domain's email.
Agent skills
This package ships the Crafty agent skills: crafty-deploy (this CLI: when to
run login/init/deploy, buildDir per framework, troubleshooting) and
crafty-cms (the MCP server and the SDK). Install them where your agent reads
skills:
crafty skills list
crafty skills install # ./.claude/skills (this project)
crafty skills install --global # ~/.claude/skills (every project)
crafty skills install --dir=<path> # another agent's skills folder
npx @craftware/crafty-cli skills install # without installing the CLI
Running it again replaces the installed copies, so it also updates them to this
CLI's version. For Claude.ai and Claude Desktop, download the zips from
docs.crafty.do (/skills/crafty-cms.zip, /skills/crafty-deploy.zip) and
upload them as skills.
Development
npm install
npm run build # tsc → dist
npm test # builds, then runs node:test
npm run typecheck
Publishing
Published as a public, scoped package under the @craftware org. publishConfig.access
is already public and prepublishOnly rebuilds dist, so no extra flags are needed.
Prerequisites: an npm account that is a member of the craftware org with publish
rights, and your 2FA device if enabled.
# 1. authenticate
npm login
npm whoami
# 2. from this package, install exact deps and validate
cd sdk/cli
npm ci
npm test
npm run typecheck
# 3. preview the exact tarball (no upload)
npm publish --dry-run
# expect: dist/**, skills/**, templates/**, LICENSE, README.md, package.json
# (never src/, tests/ or node_modules/)
# 4. publish
npm publish # add --otp=123456 if 2FA is enabled
# 5. verify
npm view @craftware/crafty-cli
npm i -g @craftware/crafty-cli && crafty --version
Releasing a new version
cd sdk/cli
npm version patch # or minor / major — creates a commit + git tag
git push --follow-tags
npm publish
Notes:
- A given version can only be published once; bump the version for any change.
- Publish from a clean tree at the intended commit.
- The
craftybin isdist/bin/crafty.js(with a#!/usr/bin/env nodeshebang). create-crafty(sdk/create-crafty, whatnpm create craftyruns) depends on this package's./createexport: publish the CLI first, thencreate-crafty.