@sleekcms/sync
v1.1.0
Published
Edit SleekCMS sites locally — models, content, templates, images — with live two-way sync and AI agent support.
Maintainers
Readme
SleekCMS CLI
Build complete websites with AI. Edit them locally. Deploy instantly.
The SleekCMS CLI brings your CMS to your code editor — with full AI-agent support baked in. Spin up an entirely new site by describing it in plain English, or drop into any existing site and edit templates, content, and styles the way you'd expect: locally, with real files, in your editor of choice.
Every save auto-syncs. Every change goes live. No build steps. No Git hooks. No infrastructure to manage.
Why developers love it
- AI generates your entire site from a description — models, templates, layouts, styles, and content, all wired up and ready to go.
- AI-generated sites are plain files — EJS templates, JSON content, CSS, JS. You own every line. Open in VS Code, Cursor, or any editor.
- Any AI can maintain it — the CLI injects
AGENT.md,CLAUDE.md, and.vscode/copilot-instructions.mdinto your workspace. GitHub Copilot, Claude, Cursor — they all read the same site reference and understand your structure from day one. - Live sync, always — save a file, it syncs. Open the SleekCMS dashboard and your preview updates in real time.
- One command to start — no installs required.
Quickstart
npx @sleekcms/sync --token <YOUR_AUTH_TOKEN>That's it. The CLI fetches your site, opens an editor prompt, and starts watching for changes. Grab a token from your SleekCMS dashboard.
Installation
Run without installing (recommended)
npx @sleekcms/sync --token <YOUR_AUTH_TOKEN>Install globally
npm install -g @sleekcms/sync
sleekcms --token <YOUR_AUTH_TOKEN>Usage
npx @sleekcms/sync [OPTIONS]| Option | Alias | Description | Default |
|--------------------|-------|--------------------------------------------------------|---------------|
| --token <token> | -t | Your SleekCMS CLI auth token (required) | — |
| --path <path> | -p | Parent directory for the local workspace | ~/.sleekcms |
| --help | -h | Show help | — |
The actual workspace folder is created at
<path>/<site-name>-<site-id>/.
# Basic
npx @sleekcms/sync --token abc123-xxxx
# Custom workspace folder
npx @sleekcms/sync -t abc123-xxxx -p ~/SitesOnce running, press r to re-fetch all files or x / Ctrl+C to exit.
Building a site with AI
This is where SleekCMS CLI becomes something different. Instead of hand-crafting every template, you describe the site you want and let an AI agent build it.
How it works
When the CLI starts for the first time, it writes three files into your local workspace:
| File | Picked up by |
|---|---|
| AGENT.md | Copilot (agent mode), any generic agent |
| CLAUDE.md | Claude / Claude Code |
| .vscode/copilot-instructions.md | GitHub Copilot in VS Code |
These files contain the complete SleekCMS site-building reference — file naming conventions, model syntax, template helpers, content format, and all the rules an AI needs to produce valid, working code.
Generate a complete site
Open the workspace in Cursor, VS Code with GitHub Copilot, or Claude Code, then describe your site:
Build a portfolio site with:
- A home page with a hero section, featured projects, and a contact form
- A blog with individual post pages
- A shared header and footer
- Tailwind CSS styling
- SEO meta tags on every pageThe AI creates the right files in the right places:
src/models/pages/home.model
src/models/pages/blog[].model
src/models/entries/header.model
src/models/entries/footer.model
src/models/blocks/hero.model
src/views/pages/home.ejs
src/views/pages/blog[].ejs
src/views/entries/header.ejs
src/views/entries/footer.ejs
src/views/blocks/hero.ejs
src/views/layouts/main.ejs
src/public/css/tailwind.css
src/content/pages/home.json
src/content/pages/blog[]/my-first-post.json
src/content/entries/header.json
src/content/entries/footer.jsonAs soon as those files hit disk, the watcher picks them up and syncs each one to SleekCMS. Your site is live before the AI finishes explaining what it built.
AI-generated sites are fully editable and maintainable — automatically
There's no lock-in, no proprietary format, no "AI black box". Every file the AI writes is the same real file you'd write by hand:
- Templates are standard EJS — open them in any editor, tweak any line.
- Models are plain JSON-like schema files — add a field, save, done.
- Content is readable JSON — edit values directly without touching the CMS dashboard.
And because AGENT.md / CLAUDE.md live in the workspace, any AI session — today or months from now — picks up the same full site context. Ask it to add a dark mode toggle, refactor the blog layout, or generate ten more blog posts. It already knows your site.
How sync works
The CLI runs a local watcher after the initial fetch. Every time you save a file:
- The watcher detects the change (debounced to batch rapid edits).
- The changed file is classified — template, model, content record, CSS, or JS.
- It's pushed to the SleekCMS API in the correct order: models first, then templates, then content.
- The SleekCMS server rebuilds and redeploys the affected pages.
On first run, the CLI pulls the full site state from the server. After that, a local .cache/ folder tracks server-known state so only real diffs are pushed — no redundant API calls.
Your editor → file save → watcher → SleekCMS API → rebuild → live siteWatch mode commands
| Key | Action |
|---|---|
| r | Re-fetch all files from server |
| x or Ctrl+C | Exit and clean up local workspace |
Previewing your site
Every change you sync is immediately reflected in SleekCMS. To preview:
- Open the SleekCMS dashboard.
- Navigate to your site.
- Click Preview — you'll see the live build with your latest changes.
No manual deploy. No waiting. SleekCMS rebuilds on every sync and the preview reflects the current state of your local workspace in real time.
Local workspace structure
<site-name>/
├── AGENT.md # AI agent reference (auto-generated)
├── CLAUDE.md # Claude reference (auto-generated)
├── .vscode/
│ ├── copilot-instructions.md # GitHub Copilot context (auto-generated)
│ └── settings.json # Editor settings
│
└── src/
├── models/
│ ├── pages/<key>.model # Page content schema
│ ├── entries/<key>.model # Entry content schema
│ └── blocks/<key>.model # Block content schema
│
├── views/
│ ├── pages/<key>.ejs # Page templates
│ ├── entries/<key>.ejs # Entry templates
│ ├── blocks/<key>.ejs # Block templates
│ └── layouts/<name>.ejs # Layout wrappers
│
├── content/
│ ├── pages/<key>.json # Single page content
│ ├── pages/<key>/<slug>.json # Collection page content (one file per item)
│ ├── entries/<key>.json # Single entry content
│ ├── entries/<key>[].json # Collection entry content (array)
│ └── images.json # Reusable site-level images (handle → shortcut)
│
└── public/
├── css/<name>.css # Stylesheets
└── js/<name>.js # ScriptsFile naming
Keys are lowercase, dash-separated. For pages, _ in the key maps to / in the URL. A [] suffix marks a collection.
| Key | URL |
|---|---|
| _index | / |
| about | /about |
| blog[] | /blog/<slug> |
| docs_getting-started | /docs/getting-started |
Content models
Models describe the shape of your content. They're JSON-like files with unquoted keys and type names as values.
{
title: text,
image: image,
body: richtext,
published: boolean
}Nested groups:
{
hero: {
heading: text,
background: image
}
}Repeatable lists:
{
features: [
{
title: text,
icon: image
}
]
}Block and entry references:
{
cta: block(cta),
author: entry(authors),
tags: [entry(tags)]
}Field types
| Type | Value |
|---|---|
| text | String |
| paragraph | Multiline string |
| richtext | HTML string |
| markdown | Markdown string |
| number | Number |
| boolean | true / false |
| date | YYYY-MM-DD |
| image | { url, alt } |
| video | { url, embed } |
| link | URL string |
| json | Object or array |
| block(key) | Embedded block object |
| entry(key) | Entry object or slug reference |
EJS template syntax
| Tag | Purpose |
|---|---|
| <%= expr %> | Output with HTML escaping |
| <%- expr %> | Output raw HTML |
| <% code %> | Execute JS (loops, conditionals) |
Template context
| Variable | Description |
|---|---|
| item | The current page, entry, or block record |
| pages | All page records |
| entries | All entries keyed by handle |
| main | Rendered page output (layouts only) |
Helper functions
Content access
| Function | Description |
|---|---|
| getPage(path) | Page by exact path |
| getPages(path, opts?) | Pages where path starts with prefix; pass { collection: true } for collection pages only |
| getEntry(handle) | Entry by handle |
| getImage(handle) | Site-level image by handle (from images.json) |
| getSlugs(path) | Slugs under a collection path |
| path(page) | Relative URL path of a page (e.g. /blog/my-post) |
| url(pathOrPage?) | Site origin, or a full absolute URL when given a path string or page object |
Rendering
| Function | Description |
|---|---|
| render(val) | Render a block or entry through its template |
| marked(md) | Convert markdown to HTML |
Images
| Function | Description |
|---|---|
| img(image, attr) | <img> element |
| src(image, attr) | Optimized image URL |
| picture(image, attr) | <picture> with dark/light variants |
| svg(image, attr?) | Inline SVG |
attr can be a "WxH" string or { w, h, class, style, fit } object.
Head injection (deduplicated automatically)
| Function | Description |
|---|---|
| title(text) | Set <title> |
| meta(attrs) | Add <meta> tag |
| link(value) | Add <link> (CSS URL, font, etc.) |
| style(css) | Inline <style> block |
| script(value) | External or inline <script> |
Tailwind: Creating
src/public/css/tailwind.cssenables Tailwind automatically — it's compiled and injected for you. Do not add it vialink().
Example templates
Layout (src/views/layouts/main.ejs)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<%- render(getEntry('header')) %>
<main><%- main %></main>
<%- render(getEntry('footer')) %>
</body>
</html>Page (src/views/pages/blog[].ejs)
<% title(item.title + ' | My Blog') %>
<% link('/css/styles.css') %>
<article>
<h1><%= item.title %></h1>
<%- img(item.cover, '1200x600') %>
<%- marked(item.body) %>
</article>Blog index listing
<% for (const post of getPages('/blog', { collection: true })) { %>
<a href="<%- path(post) %>">
<%- img(post.cover, '400x250') %>
<h3><%= post.title %></h3>
</a>
<% } %>Forms (no backend needed)
<form data-sleekcms="contact">
<input name="name" type="text" required>
<input name="email" type="email" required>
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>Any form with data-sleekcms="<name>" captures and stores submissions in the CMS dashboard automatically.
Content files
Content lives in src/content/ as plain JSON. Edit any content file and save — it syncs like any other file.
Images
Images are part of content, not a separate asset pipeline. Drop a shortcut string into any image field and the sync engine resolves it to a full { url, alt } object on save:
{
"title": "About Us",
"image": "pexels:team meeting",
"hero": {
"heading": "Hello",
"background": "unsplash:mountain sunrise|Sunrise over the mountains"
}
}Supported sources: unsplash, pexels, pixabay, iconify, url, cms. Append |<alt text> to set the image's alt.
Reusable images (src/content/images.json)
For images used in more than one place — logos, recurring icons, shared hero art — declare them once as handle → shortcut:
{
"logo": "url:https://cdn.example.com/logo.svg",
"hero": "pexels:team meeting|Our team",
"favicon": "iconify:mdi:rocket"
}Then reference any of them from any image field with "cms:<handle>" (e.g. "cms:logo"). Templates can also fetch the resolved object directly via getImage('logo').
Images in markdown
Inside markdown fields, the same shortcuts work in standard image syntax. Append |<alt> for alt text and a <width>x<height> token to set the rendered dimensions (defaults to 600x400):
Prerequisites
- Node.js v16 or later
- A SleekCMS account with a CLI auth token (get one here)
Troubleshooting
| Problem | Fix |
|---|---|
| Authentication error | Verify your token in the SleekCMS dashboard |
| No files downloaded | Check your token and --env setting |
| Changes not syncing | Check terminal output; changes are debounced by 5 seconds |
| Wrong workspace opened | Each token maps to a specific workspace folder |
| Workspace disappeared | Expected — the local workspace is wiped on Ctrl+C / x. Your edits are already on the server; re-run the CLI to pull a fresh copy. |
License
ISC — Yusuf Bhabhrawala
