Emdash source with visual editor image upload fix
Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
This commit is contained in:
164
templates/portfolio/.agents/skills/emdash-cli/EDITING-FLOW.md
Normal file
164
templates/portfolio/.agents/skills/emdash-cli/EDITING-FLOW.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# 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 |
|
||||
| `` | 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`.
|
||||
|
||||
### 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 |
|
||||
246
templates/portfolio/.agents/skills/emdash-cli/SKILL.md
Normal file
246
templates/portfolio/.agents/skills/emdash-cli/SKILL.md
Normal file
@@ -0,0 +1,246 @@
|
||||
---
|
||||
name: emdash-cli
|
||||
description: Use the EmDash CLI to manage content, schema, media, and more. Use this skill when you need to interact with a running EmDash instance from the command line — creating content, managing collections, uploading media, generating types, or scripting CMS operations.
|
||||
---
|
||||
|
||||
# EmDash CLI
|
||||
|
||||
The EmDash CLI (`emdash` or `ec`) manages EmDash CMS instances. Commands fall into two categories:
|
||||
|
||||
- **Local commands** — work directly on a SQLite file, no running server needed: `init`, `dev`, `seed`, `export-seed`, `auth secret`
|
||||
- **Remote commands** — talk to a running EmDash instance via HTTP: `types`, `login`, `logout`, `whoami`, `content`, `schema`, `media`, `search`, `taxonomy`, `menu`
|
||||
|
||||
## 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 local dev servers, just run the command — auth is handled automatically. For remote instances, run `emdash login --url https://my-site.pages.dev` first.
|
||||
|
||||
## 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 (Recommended for CI/Automation)
|
||||
|
||||
```bash
|
||||
# Single header
|
||||
npx emdash login --url https://my-site.pages.dev \
|
||||
--header "CF-Access-Client-Id: xxx.access" \
|
||||
--header "CF-Access-Client-Secret: yyy"
|
||||
|
||||
# Short form
|
||||
npx emdash login -H "CF-Access-Client-Id: xxx" -H "CF-Access-Client-Secret: yyy"
|
||||
|
||||
# Via environment (newline-separated)
|
||||
export EMDASH_HEADERS="CF-Access-Client-Id: xxx
|
||||
CF-Access-Client-Secret: yyy"
|
||||
npx emdash login --url https://my-site.pages.dev
|
||||
```
|
||||
|
||||
Headers are persisted to `~/.config/emdash/auth.json` after login, so subsequent commands inherit them automatically.
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
# Initialize database with migrations
|
||||
npx emdash init
|
||||
|
||||
# Start dev server (runs migrations, starts Astro)
|
||||
npx emdash dev
|
||||
|
||||
# Start dev server and generate types from remote
|
||||
npx emdash dev --types
|
||||
|
||||
# Apply a seed file
|
||||
npx emdash seed .emdash/seed.json
|
||||
|
||||
# Export database as seed
|
||||
npx emdash export-seed > seed.json
|
||||
npx emdash export-seed --with-content > 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 auth secret for deployment
|
||||
npx emdash auth secret
|
||||
```
|
||||
|
||||
### 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 collection
|
||||
npx emdash schema delete articles --force
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
Field types: `string`, `text`, `number`, `integer`, `boolean`, `datetime`, `image`, `reference`, `portableText`, `json`.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
```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)**.
|
||||
Reference in New Issue
Block a user