capsite: potluck, a fresh EmDash site (cloudflare:blog out of the box)

pnpm create emdash@latest potluck --pm pnpm --install --yes, per
https://docs.emdashcms.com/ ("Create your first EmDash site"); the
--yes defaults are cloudflare + blog. Replaces the old madmode/
starter (never committed).

Generated-by: create-emdash 1.2.0, run by Claude Opus 5.5
This commit is contained in:
dckc committed 2026-10-07 19:57:43 -05:00
1 parent af8dd75fd1
commit 4b4ef8fea6
52 files changed
+18118

No files matched your search

@@ -0,0 +1,180 @@
# Editing Flow
How content editing works through the CLI. Covers Portable Text conversion, `_rev` tokens, and raw mode.
## Portable Text and Markdown
EmDash stores rich text as [Portable Text](https://portabletext.org/) (PT) — a structured JSON format. The CLI automatically converts between PT and markdown so you work with a familiar text format.
### Automatic Conversion
- **On read**: PT arrays in `portableText` fields are converted to markdown strings
- **On write**: markdown strings in `portableText` fields are converted back to PT arrays
- **Non-PT fields** (string, text, number, etc.) pass through unchanged
The CLI detects which fields need conversion by fetching the collection's field schema.
### Supported Markdown Syntax
Standard blocks (lossless round-trip):
| Markdown | PT block |
| ---------------------------- | ------------------------------------------ |
| `# Heading` through `######` | h1-h6 blocks |
| Plain paragraph | normal block |
| `> Quote` | blockquote |
| `- item` / `* item` | bullet list (nesting via 2-space indent) |
| `1. item` | numbered list (nesting via 2-space indent) |
| ` ``` ```lang``` ` | code block with language |
| `![alt](url)` | image block |
Inline marks:
| Markdown | PT mark |
| ------------- | --------------- |
| `**bold**` | `strong` |
| `_italic_` | `em` |
| `` `code` `` | `code` |
| `~~strike~~` | `strikethrough` |
| `[text](url)` | link annotation |
### Unknown Blocks (Opaque Fences)
Blocks the converter doesn't recognize (custom blocks, embeds, etc.) are serialized as HTML comments:
```markdown
<!--ec:block {"_type":"callout","level":"warning","text":"Be careful"} -->
```
These survive round-trips intact. You can see and move them, but editing the JSON risks corruption. On write, they're deserialized back to the original PT block.
### Raw Mode
Skip markdown conversion entirely to work with raw PT JSON:
```bash
npx emdash content get posts 01ABC123 --raw
```
Use raw mode when:
- You need exact control over PT structure
- You're working with custom block types
- You're copying PT between items without transformation
### Writing Content
When creating or updating content, each field is checked:
- `portableText` field + **string value** → converts markdown to PT before sending
- `portableText` field + **array value** → sends as raw PT (no conversion)
- Any other field type → sends as-is
```bash
# Markdown string — converted to PT automatically
npx emdash content create posts --data '{"title": "Hello", "body": "# Welcome\n\nThis is **bold**."}'
# Raw PT array — passed through as-is
npx emdash content create posts --data '{"title": "Hello", "body": [{"_type": "block", "children": [{"_type": "span", "text": "Welcome"}]}]}'
```
## Auto-Publishing
The CLI is designed for agents. It auto-publishes on `create` and `update` by default so agents get read-after-write consistency without managing the draft/publish lifecycle.
### How It Works
- **`create`** — creates the item, then publishes it. The returned item is in `published` status.
- **`update`** — updates the item. If the collection uses revisions and the update created a draft revision, it auto-publishes to promote the draft to the content table. The returned item reflects the updated data.
- **`get`** — returns the latest state. If a pending draft exists (e.g. someone edited in the admin UI but didn't publish), the draft data is returned instead of the published data. Use `--published` to see only published data.
Use `--draft` on create/update to skip auto-publishing.
### Why Auto-Publish?
EmDash collections can support draft revisions. When they do, `update` writes data to a draft revision instead of the content table. Without auto-publish, an agent would update, then `get` the item, and see stale published data — not the changes it just made. Auto-publish eliminates this confusion.
## Read-Before-Write
Updates use `_rev` tokens for optimistic concurrency — the same principle as a file editing tool that requires you to read a file before you can edit it. You must see what you're overwriting.
### The Analogy
Think of it like a filesystem edit tool:
1. You **read** the file to see its current contents
2. You decide what to change
3. You **write** with a reference to the version you read
If someone else changed the file between your read and your write, the write fails — you can't overwrite changes you haven't seen. The `_rev` token is your proof that you've seen the current state.
### How It Works
1. `content get` returns the item with a `_rev` token in the output
2. You pass that `_rev` back to `content update` via `--rev`
3. The server checks: if the item has changed since your read, it returns **409 Conflict**
4. A successful update returns a new `_rev` for subsequent edits
### What Is a `_rev` Token?
An opaque base64 string. Don't parse it — just pass it back.
### CLI Workflow
The CLI **requires** `--rev` on updates. The typical workflow:
```bash
# 1. Read the item — note the _rev in the output
npx emdash content get posts 01ABC123
# Output includes: _rev: MToyMDI2LTAyLTE0...
# 2. Update with the _rev you received — auto-publishes by default
npx emdash content update posts 01ABC123 \
--rev MToyMDI2LTAyLTE0... \
--data '{"title": "New Title"}'
# Output shows updated item with new _rev
```
If you try to update without `--rev`, the CLI rejects the command. This ensures you always know what you're overwriting.
### Conflict Handling
If someone else updated the item between your read and write:
```
EmDashApiError: Content has been modified since last read (version conflict)
status: 409
code: CONFLICT
```
Resolution: re-read with `get`, inspect the new state, then `update` with the fresh `_rev`.
### Locked Entries
A 409 does not always mean the item changed. If someone has the entry open in the admin, the write is refused with a different code:
```
EmDashApiError: Ada is holding this entry
status: 409
code: ENTRY_LOCKED
```
Re-reading does not clear this one. The item has not changed, so a fresh `_rev` produces the same refusal. Check `code` before you retry. Either wait, or pass `--override-lock` to write anyway. An editor releases the lock when they close the entry, and a lock left behind by a crashed tab lapses seven minutes after their last heartbeat.
Overriding does not take the lock. The editor keeps it and their next save is rejected as a version conflict, so wait unless you know they have gone.
`--override-lock` is accepted by `content update`, `content delete`, `content publish`, `content unpublish` and `content schedule`. Collections with edit locking switched off never return `ENTRY_LOCKED`.
### Which Operations Need `_rev`?
Only `update`. All other operations are either idempotent or non-destructive:
| Command | `--rev` needed? | Why |
| ------------------- | --------------- | ------------------------ |
| `content create` | No | Nothing exists yet |
| `content update` | **Yes** | Overwrites existing data |
| `content delete` | No | Soft delete, reversible |
| `content publish` | No | Idempotent status change |
| `content unpublish` | No | Idempotent status change |
| `content schedule` | No | Only changes metadata |
| `content restore` | No | Restores from trash |
+267
View File
@@ -0,0 +1,267 @@
---
name: emdash-cli
description: Use the EmDash CLI to inspect and manage an EmDash instance from the command line, including content, schema, media, taxonomies, menus, search, authentication, seeds, migrations, generated types, and whole-site export and import.
---
# EmDash CLI
The EmDash CLI (`emdash`, with the short alias `em`) manages EmDash CMS instances. Commands fall into two categories:
- **Local commands** work with project files or a configured database: `init`, `doctor`, `seed`, `migrate`, `export-seed`, and `secrets`.
- **Remote commands** talk to a running EmDash instance: `types`, `login`, `logout`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`, `site`, and `plugin`.
Run `npx emdash --help` and `npx emdash <command> --help` for the installed version's exact commands and flags. Resolve the current target with a read command before a destructive or bulk mutation; examples in this skill do not authorize changing an instance the user did not place in scope.
## Authentication
Remote commands resolve auth automatically:
1. `--token` flag
2. `EMDASH_TOKEN` env var
3. Stored credentials from `emdash login`
4. Dev bypass (localhost only — no token needed)
For a localhost development server with the development bypass enabled, the client can authenticate automatically. For a remote instance, run `emdash login --url https://my-site.pages.dev` or provide a scoped token.
## Custom Headers & Reverse Proxies
Sites behind Cloudflare Access or other reverse proxies need auth headers on every request. The CLI supports this via `--header` flags and environment variables.
### Service tokens for automation
```bash
# Provide sensitive headers through the environment in CI.
export EMDASH_HEADERS="CF-Access-Client-Id: xxx
CF-Access-Client-Secret: yyy"
npx emdash whoami --url https://my-site.pages.dev
```
`emdash login --header` persists custom headers to `~/.config/emdash/auth.json` for later commands. Prefer environment-provided headers in CI so a service secret is not written to the credential file or shell history.
### Cloudflare Access Browser Flow
If you don't have service tokens and `cloudflared` is installed, the CLI will automatically:
1. Detect when Access blocks the request
2. Try to get a cached JWT via `cloudflared access token`
3. Fall back to `cloudflared access login` for browser-based auth
This works for interactive use but isn't suitable for CI. Use service tokens for automation.
### Generic Reverse Proxy Auth
The `--header` flag works with any auth scheme:
```bash
# Basic auth
npx emdash login --url https://example.com -H "Authorization: Basic dXNlcjpwYXNz"
# Custom auth header
npx emdash login --url https://example.com -H "X-API-Key: secret123"
```
## Quick Reference
### Database Setup
For normal site startup, use the project's package script. The first request runs pending migrations and applies the bundled seed when the database is empty and setup has not been completed. The Astro integration generates `emdash-env.d.ts` when the server starts.
```bash
# Start the site with its package script
pnpm dev
# Export an existing database as a seed file
# (the runtime auto-discovers .emdash/seed.json on first boot;
# `mkdir -p` because the directory may not exist yet)
mkdir -p .emdash
npx emdash export-seed > .emdash/seed.json
npx emdash export-seed --with-content=all > .emdash/seed.json
```
### Type Generation
```bash
# Generate types from local dev server
npx emdash types
# Generate from remote
npx emdash types --url https://my-site.pages.dev
# Custom output path
npx emdash types --output src/types/cms.ts
```
Writes `.emdash/types.ts` (TypeScript interfaces) and `.emdash/schema.json`.
### Authentication
```bash
# Login (OAuth Device Flow)
npx emdash login --url https://my-site.pages.dev
# Check current user
npx emdash whoami
# Logout
npx emdash logout
# Generate an encryption key for deployment
npx emdash secrets generate
```
### Content CRUD
The CLI is designed for agents. Create and update auto-publish by default so agents get read-after-write consistency without managing drafts.
```bash
# List content
npx emdash content list posts
npx emdash content list posts --status published --limit 10
# Get a single item (Portable Text fields converted to markdown)
# Returns draft data if a pending draft exists
npx emdash content get posts 01ABC123
npx emdash content get posts 01ABC123 --raw # skip PT->markdown conversion
npx emdash content get posts 01ABC123 --published # ignore pending drafts
# Create content (auto-publishes by default)
npx emdash content create posts --data '{"title": "Hello", "body": "# World"}'
npx emdash content create posts --file post.json --slug hello-world
npx emdash content create posts --draft --data '...' # keep as draft
cat post.json | npx emdash content create posts --stdin
# Update (requires --rev from a prior get, auto-publishes by default)
npx emdash content update posts 01ABC123 --rev MToyMDI2... --data '{"title": "Updated"}'
npx emdash content update posts 01ABC123 --rev MToyMDI2... --draft --data '...' # keep as draft
# Delete (soft delete)
npx emdash content delete posts 01ABC123
# Lifecycle
npx emdash content publish posts 01ABC123
npx emdash content unpublish posts 01ABC123
npx emdash content schedule posts 01ABC123 --at 2026-03-01T09:00:00Z
npx emdash content restore posts 01ABC123
```
### Schema Management
```bash
# List collections
npx emdash schema list
# Get collection with fields
npx emdash schema get posts
# Create collection
npx emdash schema create articles --label Articles --description "Blog articles"
# Delete a collection after inspecting it and confirming the target
npx emdash schema get articles
npx emdash schema delete articles
# Add field
npx emdash schema add-field posts body --type portableText --label "Body Content"
npx emdash schema add-field posts featured --type boolean --required
# Remove field
npx emdash schema remove-field posts featured
```
`schema add-field` supports the field types printed by `npx emdash schema add-field --help`. The full product schema supports additional field types that are not necessarily creatable through this command.
### Media
```bash
# List media
npx emdash media list
npx emdash media list --mime image/png
# Upload
npx emdash media upload ./photo.jpg --alt "A sunset" --caption "Bristol, 2026"
# Get / delete
npx emdash media get 01MEDIA123
npx emdash media delete 01MEDIA123
```
### Search
```bash
npx emdash search "hello world"
npx emdash search "hello" --collection posts --limit 5
```
### Taxonomies
```bash
npx emdash taxonomy list
npx emdash taxonomy terms categories
npx emdash taxonomy add-term categories --name "Tech" --slug tech
npx emdash taxonomy add-term categories --name "Frontend" --parent 01PARENT123
```
### Menus
```bash
npx emdash menu list
npx emdash menu get primary
```
### Site Export and Import
`emdash site` copies a whole site (content model, content, history, settings, and media, but no users or secrets) into a `.emdash` site package, and imports a package into an empty site. The token needs `admin`, which the `emdash login` token has, or the `transfer:export`, `transfer:analyze`, and `transfer:execute` scopes. An `INSUFFICIENT_SCOPE` error means the token has neither. A package holds every entry and the email addresses of authors and commenters: treat it like a database backup.
```bash
# Export (re-run the same command to resume after an interruption)
npx emdash site export --url https://old.example.com --output site.emdash
npx emdash site export --url https://old.example.com --output site.emdash --no-comments
# Import, step 1: upload and analyze; prints the plan and its digest
npx emdash site import site.emdash --url https://new.example.com --analyze
npx emdash site import site.emdash --url https://new.example.com --analyze \
--map-principal editor@example.com=editor@example.com --use-target-title
# Import, step 2: execute exactly the reviewed plan
npx emdash site import site.emdash --url https://new.example.com --plan sha256:<hex> --confirm
# Follow up on an import by operation id
npx emdash site import status <operation-id> --url https://new.example.com
npx emdash site import resume <operation-id> [site.emdash] --url https://new.example.com
npx emdash site import receipt <operation-id> --url https://new.example.com
# Stop an import, or lift the write block a failed or cancelled import leaves
# (neither deletes what the import wrote; --yes skips the prompt)
npx emdash site import cancel <operation-id> --url https://new.example.com
npx emdash site import abandon <operation-id> --url https://new.example.com
```
Show the user the plan (differences from the source site, warnings, blockers, principal mappings) and get their confirmation before running `--confirm`; it writes to the target site and blocks other writes there until it finishes. `site import` exits `2` when the plan has blockers, and `site import status` exits `1` for an import that failed, was cancelled or abandoned, or expired. Only cancel or abandon an import when the user asks: abandoning leaves partial data on the site, which then has to be reset before another import. `--map-principal` takes `<principal id or email>=<user id, email, or none>` and is repeatable; decision flags only work with `--analyze`, and each change produces a new plan digest. The public site is not hidden during an import, so the target should stay private until the command prints a receipt with `receiptDigestValid: true`.
## Drafts and Publishing
The CLI auto-publishes on `create` and `update` by default. This means:
- **`create`** creates the item and immediately publishes it
- **`update`** updates the item and publishes if a draft revision was created
- **`get`** returns draft data if a pending draft exists (e.g. from the admin UI)
Use `--draft` on create/update to skip auto-publishing. Use `--published` on get to ignore pending drafts.
Collections that support revisions store edits as draft revisions. The CLI handles this transparently — agents don't need to know whether a collection uses revisions or not.
## JSON Output
All remote commands support `--json` for machine-readable output. It's auto-enabled when stdout is piped. `emdash site` always writes progress to stderr, so stdout holds only the JSON result; errors are `{ "error": { "code", "message" } }`.
```bash
# Pipe to jq
npx emdash content list posts --json | jq '.items[].slug'
# Use in scripts
ID=$(npx emdash content create posts --data '{"title":"Hello"}' --json | jq -r '.id')
```
## Editing Flow
For details on how content editing works — Portable Text/markdown conversion, `_rev` tokens, and raw mode — see **[EDITING-FLOW.md](./EDITING-FLOW.md)**.