Adapto CMS CLI
Command-line interface for the Adapto CMS Management API.
Install
Quick install (macOS/Linux)
curl -sSL https://raw.githubusercontent.com/adaptocms/adapto-cms-cli/main/scripts/install.sh | bash
From source
go install github.com/adaptocms/adapto-cms-cli@latest
From releases
Download the binary for your platform from GitHub Releases.
Quick Start
Existing account:
adapto auth login --email you@example.com --password yourpassword
New account:
adapto auth register --email you@example.com --password yourpassword
# the activation email contains a ready-to-run command:
adapto auth activate --token <token-from-email>
adapto onboard # creates your first project + API key
Credentials are saved to ~/.config/adapto/credentials.json and read automatically. The flow is flag-driven so an agent can run it end to end; the only manual step is pasting the activation token from the email.
Manage content:
adapto articles list --json
adapto articles create --title "My Article" --content "Hello world" --slug my-article --author Jane --language en-US
LLM Integration
The CLI is built to be driven by LLM agents. One command gives an agent full knowledge of every command, flag, and workflow:
adapto llm-info
Pipe it into your agent's context (Claude Code, Cursor, MCP tools) and the agent can manage content end to end. Every command supports --json for machine-readable output.
Configuration
None needed in the common case: adapto auth login stores credentials in ~/.config/adapto/credentials.json and every command reads them from there.
For scripting and overrides:
| Variable |
Description |
ADAPTO_CLI_TOKEN |
Bearer token (overrides stored credentials) |
ADAPTO_CLI_API_URL |
Management API base URL (default: https://api.adaptocms.com) |
ADAPTO_CLI_TENANT_ID |
Tenant ID (overrides stored credentials) |
All variables can also be passed as flags: --token, --api-url, --tenant-id.
Two APIs. The CLI talks only to the Management API (api.adaptocms.com). The ADAPTO_API_URL / ADAPTO_API_KEY variables found in client site .env files configure the read-only Public API and are ignored by the CLI. Never point ADAPTO_CLI_API_URL at public-api.adaptocms.com.
Commands
adapto
├── api-key Manage Public API keys
│ ├── issue Issue a Public API key for the active project
│ ├── list List the active project's API keys
│ └── revoke [api-key-id] Revoke an API key
├── articles Manage articles
│ ├── archive <id> Archive an article
│ ├── categories <id> List categories of an article
│ ├── create Create an article
│ ├── create-translation <source_id> Create an article translation
│ ├── delete <id> Delete an article
│ ├── get <id> Get an article by ID
│ ├── get-by-slug <slug> Get an article by slug
│ ├── list List articles
│ ├── publish <id> Publish an article
│ ├── translations <id> List translations of an article
│ └── update <id> Update an article
├── auth Authentication commands
│ ├── activate Activate account and log in
│ ├── callback-github Complete GitHub OAuth callback
│ ├── change-password Change your password
│ ├── login Login with email and password
│ ├── login-github Login via GitHub OAuth
│ ├── login-google Login via Google credential
│ ├── logout Logout (revoke refresh token)
│ ├── me Get current user info
│ ├── orgs List your organizations and their tenants
│ ├── refresh Refresh access token
│ ├── register Register a new account
│ ├── request-password-reset Request a password reset email
│ ├── resend-activation Resend activation email
│ ├── reset-password Reset password with token
│ └── switch-tenant Switch active tenant/organization
├── categories Manage categories
│ ├── add-article <category_id> <article_id> Add an article to a category
│ ├── articles <category_id> List articles in a category
│ ├── create Create a category
│ ├── create-translation <source_id> Create a category translation
│ ├── delete <id> Delete a category
│ ├── get <id> Get a category by ID
│ ├── get-by-slug <slug> Get a category by slug
│ ├── list List categories
│ ├── remove-article <category_id> <article_id> Remove an article from a category
│ ├── subcategories <id> List subcategories
│ ├── translations <id> List translations of a category
│ └── update <id> Update a category
├── collections Manage custom collections
│ ├── create Create a collection
│ ├── delete <id> Delete a collection
│ ├── get <id> Get a collection by ID
│ ├── get-by-slug <slug> Get a collection by slug
│ ├── items Manage collection items
│ │ ├── archive <collection_id> <item_id> Archive a collection item
│ │ ├── create <collection_id> Create a collection item
│ │ ├── create-batch <collection_id> Create multiple items in batch
│ │ ├── create-translation <collection_id> <source_id> Create an item translation
│ │ ├── delete <collection_id> <item_id> Delete a collection item
│ │ ├── get <collection_id> <item_id> Get a collection item
│ │ ├── get-by-slug <collection_id> <slug> Get a collection item by slug
│ │ ├── list <collection_id> List items in a collection
│ │ ├── publish <collection_id> <item_id> Publish a collection item
│ │ ├── translations <collection_id> <item_id> List translations of an item
│ │ └── update <collection_id> <item_id> Update a collection item
│ ├── list List collections
│ └── update <id> Update a collection
├── files Manage files
│ ├── create-metadata Create file metadata (before upload)
│ ├── delete <id> Delete a file
│ ├── get <id> Get file info by ID
│ ├── list List files
│ ├── multipart-abort <file_id> <upload_id> Abort a multipart upload (low-level)
│ ├── multipart-complete <file_id> <upload_id> Complete a multipart upload (low-level)
│ ├── multipart-init <file_id> Initialize a multipart upload (low-level)
│ ├── multipart-upload <file_id> <upload_id> <part_number> <filepath> Upload one already-split part via a presigned S3 URL (low-level)
│ ├── update <id> Update file metadata
│ ├── upload <filepath> Upload a file of any size
│ └── upload-by-id <file_id> <filepath> Upload file content for an existing file record
├── llm-info Print full CLI reference for LLM consumption
├── microcopy Manage micro copy entries
│ ├── count Count micro copy entries
│ ├── create Create a micro copy entry
│ ├── create-translation <source_id> Create a micro copy translation
│ ├── delete <id> Delete a micro copy entry
│ ├── get <id> Get micro copy by ID
│ ├── get-by-key <key> Get micro copy by key
│ ├── get-by-language <language> Get all micro copy for a language
│ ├── list List micro copy entries
│ ├── translations <id> List translations of a micro copy entry
│ └── update <id> Update a micro copy entry
├── onboard Set up your first project and API key
├── org Manage organizations
│ ├── create Create an organization
│ └── list List your organizations
├── pages Manage pages
│ ├── archive <id> Archive a page
│ ├── create Create a page
│ ├── create-translation <source_id> Create a page translation
│ ├── delete <id> Delete a page
│ ├── get <id> Get a page by ID
│ ├── get-by-slug <slug> Get a page by slug
│ ├── list List pages
│ ├── publish <id> Publish a page
│ ├── translations <id> List translations of a page
│ └── update <id> Update a page
├── project Manage projects
│ ├── create Create a project
│ ├── delete [project-id] Delete a project and all its content
│ ├── list List your projects
│ ├── update [project-id] Update a project
│ └── use [project-id] Set the active project
├── status API status commands
│ └── version Get API version info
└── version Print the CLI version
Output
By default, commands output formatted tables. Use --json for JSON output:
adapto articles list --json
adapto articles get abc123 --json
Interactive Mode
When running in a terminal (TTY), missing required parameters will be prompted interactively. When piped or in scripts, missing parameters produce an error with usage hints.
File Upload
adapto files upload handles a file of any size in one command: it creates the metadata, initialises a multipart upload, requests presigned S3 URLs, streams the file to S3 in 5 MiB parts, and completes the upload.
# Single-step upload of any size (creates metadata + uploads content)
adapto files upload ./photo.jpg
# Override the stored filename, MIME type or tags
adapto files upload ./photo.jpg --filename hero.jpg --content-type image/jpeg --tags product,hero
# Two-step: create metadata first, then upload content by ID
adapto files create-metadata --filename photo.jpg --content-type image/jpeg
adapto files upload-by-id FILE_ID ./photo.jpg
File bytes go client → S3, never through the API. The Management API runs on AWS Lambda behind API Gateway, which base64-encodes binary request bodies into the invocation event (a 4/3 expansion) and caps that event at 6 MB. S3 separately requires every multipart part except the last to be at least 5 MiB — which base64-encodes to about 6.99 MB, over the cap. No chunk size satisfies both limits, so the CLI uses the API only for the control plane (metadata, init, presign, complete, abort) and PUTs the bytes straight to S3 over presigned URLs.
What that means in practice:
- The file is read from disk one part at a time — memory use does not scale with file size.
- Progress goes to stderr, so stdout (and
--json) carries only the resulting file record.
- A failure exits non-zero and aborts the multipart upload, so incomplete parts stop accruing S3 storage charges.
- If S3 rejects a part with a 403 because its presigned URL expired mid-upload, the CLI re-presigns the remaining parts and carries on.
The multipart-* subcommands expose the same sequence step by step, also over presigned URLs, for when you want to drive it yourself:
adapto files multipart-init FILE_ID --json # -> upload_id
split -b 5242880 big.zip part- # parts must be exactly 5 MiB
adapto files multipart-upload FILE_ID UPLOAD_ID 1 ./part-aa --json # -> {"ETag": "...", "PartNumber": 1}
adapto files multipart-complete FILE_ID UPLOAD_ID --parts '[{"PartNumber":1,"ETag":"<etag>"}]'
adapto files multipart-abort FILE_ID UPLOAD_ID # discard an incomplete upload
ETags are printed unquoted. The server normalises quoted, unquoted and weak-validator (W/"...") forms, so multipart-complete accepts either.
Articles, pages, and collection items support attaching media (images, videos, embeds) via the --media-json flag on create, update, and create-translation commands:
adapto articles create \
--title "My Post" \
--content "<p>Hello</p>" \
--slug my-post \
--author "Jane" \
--language en-US \
--media-json '[{
"placement_key": "hero_image",
"media_object": {
"id": "m1",
"file_id": "FILE_ID",
"url": "https://cdn.example.com/photo.jpg",
"type": "image"
},
"alt_text": "Hero image"
}]'
Each placement object supports:
placement_key: where the media goes (e.g. hero_image, body_image_1)
media_object: id, file_id, url, type (image/video/audio/document/other/youtube/vimeo/tiktok/instagram_reel/instagram_post), title, description
caption, alt_text, meta_data: optional metadata
The same --media-json flag works on adapto pages create/update and adapto collections items create/update.
Development
Building, testing, and releasing: see DEVELOPMENT.md.
License
MIT, see LICENSE.