emdash-emit 0.0.0-stage โ 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +798 -2
- package/dist/bin/emit.d.ts +5 -0
- package/dist/bin/emit.d.ts.map +1 -0
- package/dist/bin/emit.js +63 -0
- package/dist/bin/emit.js.map +1 -0
- package/dist/cli/commands/new.d.ts +4 -0
- package/dist/cli/commands/new.d.ts.map +1 -0
- package/dist/cli/commands/new.js +49 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/profile.d.ts +3 -0
- package/dist/cli/commands/profile.d.ts.map +1 -0
- package/dist/cli/commands/profile.js +104 -0
- package/dist/cli/commands/profile.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +4 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +157 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/push.d.ts +4 -0
- package/dist/cli/commands/push.d.ts.map +1 -0
- package/dist/cli/commands/push.js +107 -0
- package/dist/cli/commands/push.js.map +1 -0
- package/dist/cli/commands/status.d.ts +4 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +100 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +75 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/client/api.d.ts +19 -0
- package/dist/client/api.d.ts.map +1 -0
- package/dist/client/api.js +96 -0
- package/dist/client/api.js.map +1 -0
- package/dist/client/auth.d.ts +48 -0
- package/dist/client/auth.d.ts.map +1 -0
- package/dist/client/auth.js +165 -0
- package/dist/client/auth.js.map +1 -0
- package/dist/client/error.d.ts +22 -0
- package/dist/client/error.d.ts.map +1 -0
- package/dist/client/error.js +57 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +31 -0
- package/dist/client/http.d.ts.map +1 -0
- package/dist/client/http.js +97 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/lock.d.ts +40 -0
- package/dist/client/lock.d.ts.map +1 -0
- package/dist/client/lock.js +104 -0
- package/dist/client/lock.js.map +1 -0
- package/dist/client/mapper.d.ts +37 -0
- package/dist/client/mapper.d.ts.map +1 -0
- package/dist/client/mapper.js +142 -0
- package/dist/client/mapper.js.map +1 -0
- package/dist/client/portable-text.d.ts +94 -0
- package/dist/client/portable-text.d.ts.map +1 -0
- package/dist/client/portable-text.js +428 -0
- package/dist/client/portable-text.js.map +1 -0
- package/dist/client/schemas.d.ts +1928 -0
- package/dist/client/schemas.d.ts.map +1 -0
- package/dist/client/schemas.js +123 -0
- package/dist/client/schemas.js.map +1 -0
- package/dist/db/cache.d.ts +60 -0
- package/dist/db/cache.d.ts.map +1 -0
- package/dist/db/cache.js +303 -0
- package/dist/db/cache.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/context.d.ts +14 -0
- package/dist/mcp/context.d.ts.map +1 -0
- package/dist/mcp/context.js +23 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/profile.d.ts +6 -0
- package/dist/mcp/tools/profile.d.ts.map +1 -0
- package/dist/mcp/tools/profile.js +81 -0
- package/dist/mcp/tools/profile.js.map +1 -0
- package/dist/mcp/tools/sync-diff.d.ts +4 -0
- package/dist/mcp/tools/sync-diff.d.ts.map +1 -0
- package/dist/mcp/tools/sync-diff.js +115 -0
- package/dist/mcp/tools/sync-diff.js.map +1 -0
- package/dist/mcp/tools/sync-new.d.ts +4 -0
- package/dist/mcp/tools/sync-new.d.ts.map +1 -0
- package/dist/mcp/tools/sync-new.js +54 -0
- package/dist/mcp/tools/sync-new.js.map +1 -0
- package/dist/mcp/tools/sync-pull.d.ts +4 -0
- package/dist/mcp/tools/sync-pull.d.ts.map +1 -0
- package/dist/mcp/tools/sync-pull.js +180 -0
- package/dist/mcp/tools/sync-pull.js.map +1 -0
- package/dist/mcp/tools/sync-push.d.ts +4 -0
- package/dist/mcp/tools/sync-push.d.ts.map +1 -0
- package/dist/mcp/tools/sync-push.js +105 -0
- package/dist/mcp/tools/sync-push.js.map +1 -0
- package/dist/mcp/tools/sync-status.d.ts +4 -0
- package/dist/mcp/tools/sync-status.d.ts.map +1 -0
- package/dist/mcp/tools/sync-status.js +45 -0
- package/dist/mcp/tools/sync-status.js.map +1 -0
- package/dist/sync/bylines.d.ts +23 -0
- package/dist/sync/bylines.d.ts.map +1 -0
- package/dist/sync/bylines.js +112 -0
- package/dist/sync/bylines.js.map +1 -0
- package/dist/sync/diff.d.ts +16 -0
- package/dist/sync/diff.d.ts.map +1 -0
- package/dist/sync/diff.js +68 -0
- package/dist/sync/diff.js.map +1 -0
- package/dist/sync/media.d.ts +37 -0
- package/dist/sync/media.d.ts.map +1 -0
- package/dist/sync/media.js +191 -0
- package/dist/sync/media.js.map +1 -0
- package/dist/sync/merge.d.ts +36 -0
- package/dist/sync/merge.d.ts.map +1 -0
- package/dist/sync/merge.js +112 -0
- package/dist/sync/merge.js.map +1 -0
- package/dist/sync/new.d.ts +24 -0
- package/dist/sync/new.d.ts.map +1 -0
- package/dist/sync/new.js +75 -0
- package/dist/sync/new.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +55 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +222 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/sync/preflight.d.ts +25 -0
- package/dist/sync/preflight.d.ts.map +1 -0
- package/dist/sync/preflight.js +128 -0
- package/dist/sync/preflight.js.map +1 -0
- package/dist/sync/pull.d.ts +67 -0
- package/dist/sync/pull.d.ts.map +1 -0
- package/dist/sync/pull.js +405 -0
- package/dist/sync/pull.js.map +1 -0
- package/dist/sync/push.d.ts +33 -0
- package/dist/sync/push.d.ts.map +1 -0
- package/dist/sync/push.js +432 -0
- package/dist/sync/push.js.map +1 -0
- package/dist/sync/status.d.ts +24 -0
- package/dist/sync/status.d.ts.map +1 -0
- package/dist/sync/status.js +85 -0
- package/dist/sync/status.js.map +1 -0
- package/dist/sync/taxonomies.d.ts +49 -0
- package/dist/sync/taxonomies.d.ts.map +1 -0
- package/dist/sync/taxonomies.js +220 -0
- package/dist/sync/taxonomies.js.map +1 -0
- package/dist/utils/config.d.ts +18 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +79 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +115 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +147 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/naming.d.ts +28 -0
- package/dist/utils/naming.d.ts.map +1 -0
- package/dist/utils/naming.js +94 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/utils/profile.d.ts +72 -0
- package/dist/utils/profile.d.ts.map +1 -0
- package/dist/utils/profile.js +180 -0
- package/dist/utils/profile.js.map +1 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +43 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/workspace.d.ts +22 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +74 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +68 -4
package/README.md
CHANGED
|
@@ -1,3 +1,799 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Emit (`emdash-emit`)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/aroooy/emdash-emit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/emdash-emit)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
**Emit** is an AI-first local workspace and bidirectional synchronization tool for [EmDash](https://github.com/aroooy/emdash) (Cloudflare Workers + D1). It provides a full-featured CLI and a Model Context Protocol (MCP) server designed for autonomous coding agents (Antigravity, Claude Code, Cursor) and human writers.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ๐ Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Overview & Architecture Philosophy](#-overview--architecture-philosophy)
|
|
15
|
+
- [The Authoring Desk Philosophy & Non-Goals](#1-the-authoring-desk-philosophy--non-goals)
|
|
16
|
+
- [The Sliding Window "Staging Desk" Model](#2-the-sliding-window-staging-desk-model)
|
|
17
|
+
- [Unpushed Edits Safeguard](#3-unpushed-edits-safeguard)
|
|
18
|
+
- [Pure TypeScript & Zero Native Builds](#4-pure-typescript--zero-native-builds)
|
|
19
|
+
- [Distributed Mutex Lock & 3-Way Merge](#5-distributed-mutex-lock--3-way-merge)
|
|
20
|
+
- [๐ Key Features](#-key-features)
|
|
21
|
+
- [๐ฆ Installation](#-installation)
|
|
22
|
+
- [๐ Initial Setup & Profiles](#-initial-setup--profiles)
|
|
23
|
+
- [Register a Profile](#1-register-a-profile)
|
|
24
|
+
- [Initialize a Workspace](#2-initialize-a-workspace)
|
|
25
|
+
- [Multi-Site Management](#3-multi-site-management)
|
|
26
|
+
- [Recommended `.gitignore`](#4-recommended-gitignore)
|
|
27
|
+
- [๐ Directory Structure & Data Specifications](#-directory-structure--data-specifications)
|
|
28
|
+
- [๐ Markdown & YAML Frontmatter Specification](#-markdown--yaml-frontmatter-specification)
|
|
29
|
+
- [Frontmatter Schema](#frontmatter-schema)
|
|
30
|
+
- [Critical Content Rules](#critical-content-rules)
|
|
31
|
+
- [EmDash Custom Block Preservation](#emdash-custom-block-preservation)
|
|
32
|
+
- [Media Management & Upload Workflow](#media-management--upload-workflow)
|
|
33
|
+
- [๐ค The Golden Path for AI & Humans (4-Step Cycle)](#-the-golden-path-for-ai--humans-4-step-cycle)
|
|
34
|
+
- [The 4-Step Authoring Cycle](#the-4-step-authoring-cycle)
|
|
35
|
+
- [Preflight Safety Guardrails (G1โG9)](#preflight-safety-guardrails-g1g9)
|
|
36
|
+
- [๐ CLI Command Reference](#-cli-command-reference)
|
|
37
|
+
- [`emit init`](#emit-init)
|
|
38
|
+
- [`emit profile`](#emit-profile)
|
|
39
|
+
- [`emit new`](#emit-new)
|
|
40
|
+
- [`emit pull`](#emit-pull)
|
|
41
|
+
- [`emit push`](#emit-push)
|
|
42
|
+
- [`emit status`](#emit-status)
|
|
43
|
+
- [`emit diff`](#emit-diff)
|
|
44
|
+
- [`emit pull-taxonomies`](#emit-pull-taxonomies)
|
|
45
|
+
- [`emit pull-media`](#emit-pull-media)
|
|
46
|
+
- [`emit guide`](#emit-guide)
|
|
47
|
+
- [`emit mcp`](#emit-mcp)
|
|
48
|
+
- [๐ค Model Context Protocol (MCP) Support](#-model-context-protocol-mcp-support)
|
|
49
|
+
- [Setup Guide](#setup-guide)
|
|
50
|
+
- [Available MCP Tools](#available-mcp-tools)
|
|
51
|
+
- [Autonomous Agent Workflows](#autonomous-agent-workflows)
|
|
52
|
+
- [๐ Distributed Concurrency & 3-Way Merge](#-distributed-concurrency--3-way-merge)
|
|
53
|
+
- [๐ง Troubleshooting & FAQ](#-troubleshooting--faq)
|
|
54
|
+
- [๐งช Testing & Quality Assurance](#-testing--quality-assurance)
|
|
55
|
+
- [๐ License & Copyright](#-license--copyright)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## ๐ก Overview & Architecture Philosophy
|
|
60
|
+
|
|
61
|
+
EmDash is a modern serverless headless CMS built on Cloudflare Workers and D1. While Cloudflare D1 provides blazing-fast edge performance, authoring long-form blog posts and articles directly in a web dashboard can be cumbersome.
|
|
62
|
+
|
|
63
|
+
**Emit** bridges this gap. It turns any local directory into a Git-like local authoring workspace, allowing you and AI coding agents to edit Markdown files using your favorite editors (VS Code, Neovim, Cursor, Antigravity) and seamlessly sync them with EmDash.
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
67
|
+
โ Local Workspace โ emit push / pull โ Remote EmDash Server โ
|
|
68
|
+
โ posts/draft/*.md โ โโโโโโโโโโโโโโโโโโบ โ Cloudflare Workers โ
|
|
69
|
+
โ posts/published/*.md โ (AST + Frontmatter)โ Cloudflare D1 SQL โ
|
|
70
|
+
โ .emit/cache.db โ โ Cloudflare R2 Media โ
|
|
71
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 1. The Authoring Desk Philosophy & Non-Goals
|
|
76
|
+
|
|
77
|
+
To prevent user expectations from misaligning with the tool's purpose, it is important to clarify **what Emit is and what it is not**:
|
|
78
|
+
|
|
79
|
+
* **What Emit IS (Core Goal)**:
|
|
80
|
+
* An **active authoring and editing workbench (Staging Desk)** where human authors and AI coding agents collaborate seamlessly.
|
|
81
|
+
* A tool focused on drafting, reviewing, polishing, diffing, and publishing current and recent articles with strict safety guards.
|
|
82
|
+
* **What Emit is NOT (Non-Goals)**:
|
|
83
|
+
* **Not a full-site database archiver / backup dumper**: Emit is not designed to clone an entire 15-year history of 20,000 blog posts onto your laptop.
|
|
84
|
+
* **Not an offline bulk mass-migration converter**: Emit does not perform batch transformation across thousands of historical posts at once.
|
|
85
|
+
|
|
86
|
+
If your site contains thousands of articles, they remain safe, searchable, and intact in your Cloudflare D1 database. Emit only pulls the active working set into your local workspace, keeping your directory clean and searches instant. Need to edit a specific older article outside the active window? Simply pull it on demand using `emit pull <article-id>`.
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> **Can the sync limit be adjusted?**
|
|
90
|
+
> Yes. While `--all` is intentionally omitted from the CLI to protect the sliding window discipline, you can customize the window size by setting `postsSyncLimit: 300` (or any desired number) in `.emit/config.json`. Emit's internal pagination engine will automatically batch-fetch up to that configured limit.
|
|
91
|
+
|
|
92
|
+
### 2. The Sliding Window "Staging Desk" Model
|
|
93
|
+
|
|
94
|
+
Traditional synchronization tools attempt to download the entire history of a site. If your blog has thousands of articles, local directories become cluttered, Ripgrep/VSCode search slows down, and AI context windows get overwhelmed with irrelevant historical data.
|
|
95
|
+
|
|
96
|
+
Emit adopts a **bounded sliding window**:
|
|
97
|
+
|
|
98
|
+
* It maintains an active sliding window of recent posts (default: **latest 100 posts**).
|
|
99
|
+
* When older posts fall outside the sliding window during `emit pull`, Emit cleanly **prunes them from disk**, keeping your local repository lean, fast, and organized.
|
|
100
|
+
* Any pruned article can be pulled back into the workspace at any time with `emit pull <id>`.
|
|
101
|
+
|
|
102
|
+
### 3. Unpushed Edits Safeguard
|
|
103
|
+
|
|
104
|
+
What happens if an article with unpushed local modifications is pushed out of the sliding window?
|
|
105
|
+
Emit guarantees **zero data loss**:
|
|
106
|
+
|
|
107
|
+
* If an article has local modifications not yet pushed to the remote server, Emit's pruning engine **aborts the pull immediately** with a `SyncPruneConflictError`.
|
|
108
|
+
* It lists the exact unpushed file and informs the user to either push the changes (`emit push <file>`) or discard them before proceeding.
|
|
109
|
+
|
|
110
|
+
### 4. Pure TypeScript & Zero Native Builds
|
|
111
|
+
|
|
112
|
+
Emit has **zero C++ native dependencies** (`node-gyp`).
|
|
113
|
+
By leveraging Node.js 22's built-in `node:sqlite` module (`DatabaseSync`), Emit installs in seconds on macOS, Linux, and Windows without requiring system build tools or Python.
|
|
114
|
+
|
|
115
|
+
### 5. Distributed Mutex Lock & 3-Way Merge
|
|
116
|
+
|
|
117
|
+
To prevent concurrent race conditions when multiple authors or AI agents collaborate:
|
|
118
|
+
|
|
119
|
+
* Emit acquires a distributed 7-minute lease via `POST /api/v1/content/lock` before writing to remote articles, with automatic heartbeats and `try/finally` release guarantees.
|
|
120
|
+
* If concurrent edits occur, Emit utilizes a **Hybrid 3-Way Merge** engine (`node-diff3`), merging YAML frontmatter fields and Markdown body text independently.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## ๐ Key Features
|
|
125
|
+
|
|
126
|
+
* **AI-First Authoring Workflow**: Built specifically for autonomous agents and humans to draft, inspect, and sync local Markdown files safely.
|
|
127
|
+
* **Sliding Window Synchronization**: Automatically manages an active sliding window of recent posts (default: latest 100), pruning older cached entries from disk.
|
|
128
|
+
* **Unpushed Edits Safeguard**: Detects unpushed local changes outside the sync window and aborts sync safely before any files can be deleted.
|
|
129
|
+
* **Git-Style Workspace Encapsulation**: Houses local SQLite cache (`node:sqlite`) and workspace configuration neatly inside a `.emit/` hidden folder.
|
|
130
|
+
* **Multi-Site Profile Management**: Manage multiple EmDash sites and credentials in `~/.config/emit/profiles.yaml` with automatic workspace directory detection.
|
|
131
|
+
* **Hybrid 3-Way Merge Engine**: Resolves concurrent changes between remote and local versions like Git, independently merging YAML frontmatter and Markdown body.
|
|
132
|
+
* **Distributed Mutual Exclusion Lock**: Acquires 7-minute leases via `POST /lock` with `try/finally` release semantics to eliminate race conditions during publishing.
|
|
133
|
+
* **Opaque Block Preservation**: Preserves EmDash custom blocks (e.g., `core:gallery`) losslessly across AST roundtrips via `<!--ec:block ... -->` fences.
|
|
134
|
+
* **Taxonomy & Media Synchronization**: Sync category/tag YAML schemas and download media assets with metadata.
|
|
135
|
+
* **Zero Native Dependencies**: Completely eliminates native C++ builds (`node-gyp`) by leveraging Node.js 22+ built-in `node:sqlite`.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## ๐ฆ Installation
|
|
140
|
+
|
|
141
|
+
Emit requires **Node.js v22.0.0** or higher.
|
|
142
|
+
|
|
143
|
+
### Method A: Global Installation via npm (Recommended)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install -g emdash-emit
|
|
147
|
+
|
|
148
|
+
# Verify installation
|
|
149
|
+
emit --version
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Method B: Temporary Execution via npx
|
|
154
|
+
|
|
155
|
+
Run commands without installing globally:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx emdash-emit status
|
|
159
|
+
npx emdash-emit pull
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Method C: Local Development Link
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git clone https://github.com/aroooy/emdash-emit.git
|
|
167
|
+
cd emdash-emit
|
|
168
|
+
npm install
|
|
169
|
+
npm run build
|
|
170
|
+
npm install -g .
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## ๐ Initial Setup & Profiles
|
|
177
|
+
|
|
178
|
+
### 1. Register a Profile
|
|
179
|
+
|
|
180
|
+
Add your EmDash instance credentials to the global profile registry:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
emit profile add my-blog \
|
|
184
|
+
--origin https://your-blog.example.com \
|
|
185
|
+
--token <your-personal-access-token> \
|
|
186
|
+
--workspace /path/to/local/blog-repo
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
* `--origin`: The base URL of your EmDash Cloudflare Workers instance.
|
|
190
|
+
* `--token`: Your Personal Access Token (PAT) generated from the EmDash dashboard.
|
|
191
|
+
* `--workspace`: The local directory where articles and assets will be synchronized.
|
|
192
|
+
|
|
193
|
+
> [!NOTE]
|
|
194
|
+
> Profiles are securely stored with `0600` permissions at `~/.config/emit/profiles.yaml` (on macOS / Linux) or `%USERPROFILE%\.config\emit\profiles.yaml` (on Windows).
|
|
195
|
+
|
|
196
|
+
### 2. Initialize a Workspace
|
|
197
|
+
|
|
198
|
+
Navigate to your local repository directory and initialize it:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cd /path/to/local/blog-repo
|
|
202
|
+
emit init
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
This creates the `.emit/` directory with a default `config.json` and initializes the local baseline cache SQLite database (`.emit/cache.db`).
|
|
206
|
+
|
|
207
|
+
### 3. Multi-Site Management (Directory-Driven & Zero Friction)
|
|
208
|
+
|
|
209
|
+
Emit is designed from the ground up for authors who manage multiple blogs or environments (e.g. personal blog, company blog, staging site).
|
|
210
|
+
|
|
211
|
+
Because each profile records its own `--workspace` directory, **you never have to manually switch global "active" sites**. Emit resolves the target site automatically based on where you are:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Register personal blog
|
|
215
|
+
emit profile add personal --origin https://personal.example.com --token <pat-1> --workspace ~/blogs/personal
|
|
216
|
+
|
|
217
|
+
# Register company blog
|
|
218
|
+
emit profile add work --origin https://blog.company.example.com --token <pat-2> --workspace ~/blogs/work
|
|
219
|
+
|
|
220
|
+
# List all registered profiles
|
|
221
|
+
emit profile list
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**How Automatic Profile Resolution Works:**
|
|
225
|
+
1. **Directory-Driven Auto Detection (Recommended)**: When you `cd ~/blogs/personal` and run `emit pull`, `emit push`, or launch an AI agent, Emit detects your current folder and automatically uses the `personal` profile. When you `cd ~/blogs/work`, it seamlessly connects to `work`.
|
|
226
|
+
2. **Explicit Profile Flag**: You can explicitly specify `--profile <name>` (or pass `profile: "<name>"` in MCP) from any directory to override automatic detection.
|
|
227
|
+
3. **Ambiguity Safeguard**: If you run a command outside of any registered workspace and omit `--profile`, Emit aborts with an `AmbiguousProfileError` instead of guessing or silently pushing to the wrong site.
|
|
228
|
+
|
|
229
|
+
### 4. Recommended `.gitignore`
|
|
230
|
+
|
|
231
|
+
When tracking your local blog repository with Git, use the following recommended `.gitignore` configuration:
|
|
232
|
+
|
|
233
|
+
```gitignore
|
|
234
|
+
# Emit Local Cache Database (Strictly exclude binary SQLite DBs)
|
|
235
|
+
.emit/cache.db*
|
|
236
|
+
.emit/*.db*
|
|
237
|
+
*.db
|
|
238
|
+
*.db-wal
|
|
239
|
+
*.db-shm
|
|
240
|
+
|
|
241
|
+
# OS & Editor temporary files
|
|
242
|
+
.DS_Store
|
|
243
|
+
Thumbs.db
|
|
244
|
+
Temp/
|
|
245
|
+
tmp/
|
|
246
|
+
|
|
247
|
+
# Optional: Media binaries (Uncomment if you prefer not to commit large images to Git)
|
|
248
|
+
# media/*.jpg
|
|
249
|
+
# media/*.png
|
|
250
|
+
# media/*.webp
|
|
251
|
+
# media/*.avif
|
|
252
|
+
# media/*.mp4
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
> [!TIP]
|
|
257
|
+
> * `.emit/cache.db*` **must be excluded** from Git because it is a machine-local SQLite tracking database.
|
|
258
|
+
> * `.emit/config.json` **can be safely committed** to Git because it contains workspace configuration (origin URL, default collections), while sensitive authentication tokens remain securely isolated in `~/.config/emit/profiles.yaml`.
|
|
259
|
+
> * `media/*.yaml` metadata files can be committed to Git to track asset metadata alongside your Markdown articles.
|
|
260
|
+
>
|
|
261
|
+
>
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## ๐ Directory Structure & Data Specifications
|
|
266
|
+
|
|
267
|
+
When synchronized, your local workspace will be organized as follows:
|
|
268
|
+
|
|
269
|
+
```text
|
|
270
|
+
your-blog/
|
|
271
|
+
โโโ .emit/ # [Hidden System Directory - Do not edit manually]
|
|
272
|
+
โ โโโ config.json # Workspace configuration (origin, postsSyncLimit, etc.)
|
|
273
|
+
โ โโโ cache.db # SQLite baseline tracking & hash database
|
|
274
|
+
โโโ categories/ # Category definition files
|
|
275
|
+
โ โโโ tech.yaml
|
|
276
|
+
โ โโโ lifestyle.yaml
|
|
277
|
+
โโโ tags/ # Tag definition files
|
|
278
|
+
โ โโโ typescript.yaml
|
|
279
|
+
โ โโโ ai.yaml
|
|
280
|
+
โโโ media/ # Media files and metadata
|
|
281
|
+
โ โโโ 01M6ABCDEF.jpg
|
|
282
|
+
โ โโโ 01M6ABCDEF.jpg.yaml
|
|
283
|
+
โโโ posts/ # Local Markdown Articles
|
|
284
|
+
โโโ draft/ # Draft articles (<yyyyMMdd>_<ulid>-<slug>.md)
|
|
285
|
+
โ โโโ 20261104_01M6...-my-new-post.md
|
|
286
|
+
โโโ published/ # Live published articles (<yyyyMMdd>_<ulid>-<slug>.md)
|
|
287
|
+
โโโ 20261015_01M5...-getting-started.md
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## ๐ Markdown & YAML Frontmatter Specification
|
|
294
|
+
|
|
295
|
+
Each article is stored as a Markdown file with a standard YAML frontmatter header.
|
|
296
|
+
|
|
297
|
+
### Frontmatter Schema
|
|
298
|
+
|
|
299
|
+
```yaml
|
|
300
|
+
---
|
|
301
|
+
id: 01M6ABCDEF1234567890123456 # EmDash canonical ULID (auto-generated; DO NOT EDIT)
|
|
302
|
+
title: "Building AI-Powered CMS Tools"
|
|
303
|
+
slug: "building-ai-powered-cms-tools"
|
|
304
|
+
status: draft # Status: "draft" or "published"
|
|
305
|
+
publishedAt: null # ISO 8601 timestamp (null for drafts)
|
|
306
|
+
updatedAt: "2026-11-04T12:00:00.000Z"
|
|
307
|
+
excerpt: "A deep dive into building autonomous content workflows with EmDash and Emit."
|
|
308
|
+
featuredImage: "01M6MEDIA123456" # Media ULID or null
|
|
309
|
+
categories: # Slugs matching definitions in categories/*.yaml
|
|
310
|
+
- tech
|
|
311
|
+
tags: # Slugs matching definitions in tags/*.yaml
|
|
312
|
+
- typescript
|
|
313
|
+
- ai
|
|
314
|
+
customFields: # Optional custom field definitions
|
|
315
|
+
canonicalUrl: ""
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
Markdown body begins here...
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Critical Content Rules
|
|
323
|
+
|
|
324
|
+
1. **Do Not Duplicate the Title in the Body**:
|
|
325
|
+
- The article title is managed by the `title:` field in the frontmatter.
|
|
326
|
+
- **Do not** add `# Article Title` as the first heading in the Markdown body. Doing so causes duplicate title displays on the site frontend. (Emit's preflight guard G7 will detect and automatically trim redundant top headings).
|
|
327
|
+
2. **Taxonomy Slugs**:
|
|
328
|
+
- Items under `categories:` and `tags:` must match existing slug names from `categories/` and `tags/`.
|
|
329
|
+
- Never invent arbitrary IDs or invalid names; pull taxonomies with `emit pull-taxonomies` to see available options.
|
|
330
|
+
3. **Status Transitions**:
|
|
331
|
+
- To publish a draft, update `status: published` in the frontmatter and execute `emit push <filePath> --allow-publish`.
|
|
332
|
+
- Emit automatically updates the remote state and moves the file from `posts/draft/` to `posts/published/`.
|
|
333
|
+
- **Never move or rename files manually using `mv`**.
|
|
334
|
+
|
|
335
|
+
### EmDash Custom Block Preservation
|
|
336
|
+
|
|
337
|
+
EmDash supports rich visual blocks (e.g., galleries, callouts, audio players). When converted from EmDash's internal AST to Markdown, Emit wraps non-standard blocks in opaque comment fences:
|
|
338
|
+
|
|
339
|
+
```html
|
|
340
|
+
<!--ec:block type="core:gallery" id="01M6XYZ..." data="{\"columns\":3,\"images\":[...]}" -->
|
|
341
|
+
<!--/ec:block-->
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
* **Rule**: Do not modify or delete these comment fences unless you intentionally want to remove the block. Emit preserves them losslessly on round-trips.
|
|
346
|
+
|
|
347
|
+
### Media Management & Upload Workflow
|
|
348
|
+
|
|
349
|
+
EmDash stores media files (images, documents) in Cloudflare R2 via multipart upload endpoints.
|
|
350
|
+
|
|
351
|
+
**How to add and use images in your articles:**
|
|
352
|
+
|
|
353
|
+
1. **Upload New Images**: Upload images via the **EmDash Admin Web Dashboard** (`/admin/media`) or via the official EmDash MCP tool (`media_upload`). This generates a canonical ULID and uploads the asset to Cloudflare R2.
|
|
354
|
+
2. **Sync Media Locally**: Run `emit pull-media` in your workspace. This downloads the images and companion `.yaml` metadata files into `media/`.
|
|
355
|
+
3. **Embed in Articles**:
|
|
356
|
+
- For featured hero images: set `featuredImage: "01M6..."` in frontmatter.
|
|
357
|
+
- For inline images in body: use standard Markdown syntax with remote URLs: ``.
|
|
358
|
+
|
|
359
|
+
> [!WARNING]
|
|
360
|
+
> **No Local Image Paths in Markdown**: Preflight Guard **G5** strictly forbids pushing articles containing local filesystem paths like ``. Images must be uploaded to Cloudflare R2 before publishing to prevent broken images in production.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## ๐ค The Golden Path for AI & Humans (4-Step Cycle)
|
|
365
|
+
|
|
366
|
+
Whether you are a human developer or an autonomous AI agent (Antigravity, Claude Code, Cursor), always follow the **4-Step Cycle**:
|
|
367
|
+
|
|
368
|
+
```text
|
|
369
|
+
[1. CHECK] Inspect local workspace state โโโบ emit status
|
|
370
|
+
โ
|
|
371
|
+
[2. PULL] Sync remote updates safely โโโบ emit pull
|
|
372
|
+
โ
|
|
373
|
+
[3. EDIT] Write & refine Markdown directly โโโบ posts/draft/*.md
|
|
374
|
+
โ
|
|
375
|
+
[4. PUSH] Push changes to remote โโโบ emit push <filePath>
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### The 4-Step Authoring Cycle
|
|
380
|
+
|
|
381
|
+
1. **CHECK (`emit status`)**: Check your workspace status to see synced, modified, and untracked files.
|
|
382
|
+
2. **PULL (`emit pull`)**: Synchronize the latest sliding window from the server into your local workspace.
|
|
383
|
+
3. **EDIT (Direct Editing)**:
|
|
384
|
+
* For new posts: run `emit new <slug> --title "My Title"` to scaffold a canonical draft.
|
|
385
|
+
* Edit the Markdown body and YAML frontmatter in `posts/draft/` or `posts/published/`.
|
|
386
|
+
|
|
387
|
+
4. **PUSH (`emit push`)**:
|
|
388
|
+
* To push a draft: `emit push posts/draft/<filename>.md`
|
|
389
|
+
* To publish live: update `status: published` and run `emit push posts/draft/<filename>.md --allow-publish`.
|
|
390
|
+
|
|
391
|
+
### Preflight Safety Guardrails (G1โG9)
|
|
392
|
+
|
|
393
|
+
To ensure autonomous AI agents and automated scripts cannot corrupt workspace state or accidentally publish unintended content, Emit enforces 9 strict preflight guardrails before executing any write operation:
|
|
394
|
+
|
|
395
|
+
| Guard | Code Name | Description | Severity |
|
|
396
|
+
| --- | --- | --- | --- |
|
|
397
|
+
| **G1** | `SINGLE_TARGET_GUARD` | Enforces explicit target (`filePath`, `id`, or `--all`) for push/pull; forbids blind bulk push without inspection | Error |
|
|
398
|
+
| **G2** | `ID_MISMATCH_ERROR` | Verifies that frontmatter `id` exactly matches the filename ULID; forbids manual `mv` renames | Error |
|
|
399
|
+
| **G3** | `UNINTENDED_PUBLISH_GUARD` | Rejects promoting `draft` to `published` unless `--allow-publish` (`allowPublish: true`) is explicitly given | Error |
|
|
400
|
+
| **G4** | `INVALID_TAXONOMY_ERROR` | Validates category and tag slugs against local YAML definitions to stop AI taxonomy hallucination | Error |
|
|
401
|
+
| **G5** | `LOCAL_IMAGE_DETECTED` | Detects unresolved local image paths (``); requires remote R2 URLs | Error |
|
|
402
|
+
| **G6** | `UNRESOLVED_CONFLICT_MARKERS` | Scans for Git 3-way merge conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and blocks push | Error |
|
|
403
|
+
| **G7** | `TITLE_DUPLICATION_AUTOTRIM` | Detects redundant `# Title` headings at the top of the body and automatically trims them safely | Warning |
|
|
404
|
+
| **G8** | `UNSUPPORTED_SYNTAX_WARNING` | Warns if unsupported block HTML or incompatible constructs are detected in Markdown | Warning |
|
|
405
|
+
| **G9** | `MODIFIED_OPAQUE_BLOCK` | Verifies that `<!--ec:block ... -->` opaque fences have not been tampered with or corrupted | Error |
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## ๐ CLI Command Reference
|
|
410
|
+
|
|
411
|
+
### `emit init`
|
|
412
|
+
|
|
413
|
+
Initializes the current directory as an Emit local workspace.
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
emit init [--origin <url>]
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
* `--origin <url>`: Optional EmDash origin URL. If omitted, uses the active profile.
|
|
421
|
+
|
|
422
|
+
**Example Output:**
|
|
423
|
+
|
|
424
|
+
```text
|
|
425
|
+
โ
Initialized Emit workspace at: /Volumes/ext/github/my-blog
|
|
426
|
+
Config: /Volumes/ext/github/my-blog/.emit/config.json
|
|
427
|
+
Database: /Volumes/ext/github/my-blog/.emit/cache.db
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### `emit profile`
|
|
434
|
+
|
|
435
|
+
Manage connection profiles across multiple EmDash instances.
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
# List all configured profiles and their workspace directories
|
|
439
|
+
emit profile list
|
|
440
|
+
|
|
441
|
+
# Add or update a profile
|
|
442
|
+
emit profile add <name> --origin <url> [--token <pat>] [--workspace <path>]
|
|
443
|
+
|
|
444
|
+
# Show currently resolved profile for this directory
|
|
445
|
+
emit profile current
|
|
446
|
+
|
|
447
|
+
# Remove a profile
|
|
448
|
+
emit profile remove <name>
|
|
449
|
+
|
|
450
|
+
# (Optional) Set a fallback default profile for when operating outside any workspace
|
|
451
|
+
emit profile use <name>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### `emit new`
|
|
457
|
+
|
|
458
|
+
Scaffolds a new draft article with a canonical 26-character ULID timestamp.
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
emit new <slug> [-t, --title <title>] [-c, --collection <collection>] [-l, --locale <locale>]
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
* `<slug>`: URL slug for the article (e.g., `modern-web-architecture`).
|
|
466
|
+
* `-t, --title <title>`: Human-readable article title.
|
|
467
|
+
* `-c, --collection <name>`: Collection name (defaults to `posts`).
|
|
468
|
+
* `-l, --locale <locale>`: Locale code (defaults to `ja` or config).
|
|
469
|
+
|
|
470
|
+
**Example:**
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
emit new ai-workflow-guide --title "AI Workflow Guide"
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Output:**
|
|
478
|
+
|
|
479
|
+
```text
|
|
480
|
+
โ
Created new article scaffold (ID: 01M6ABCDEF1234567890123456)
|
|
481
|
+
Path: posts/draft/20261104_01M6ABCDEF1234567890123456-ai-workflow-guide.md
|
|
482
|
+
|
|
483
|
+
Next: Write your content in Markdown, then run:
|
|
484
|
+
emit push posts/draft/20261104_01M6ABCDEF1234567890123456-ai-workflow-guide.md
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
### `emit pull`
|
|
491
|
+
|
|
492
|
+
Synchronizes remote articles into the local workspace sliding window.
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
emit pull [id] [options]
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
* `[id]`: Optional article ID or ULID. If specified, pulls only that specific article (even if outside the window).
|
|
500
|
+
* `-s, --status <draft|published|all>`: Filter by publication status (default: `all`).
|
|
501
|
+
* `-c, --collection <collection>`: Collection name (default: `posts`).
|
|
502
|
+
* `--no-prune`: Do not prune local articles that have fallen outside the sliding window (escape hatch; not intended for routine use).
|
|
503
|
+
* `--force`: Force overwrite local files and bypass differential hash checking.
|
|
504
|
+
* `--concurrency <n>`: Parallel processing task limit (default: `8`).
|
|
505
|
+
|
|
506
|
+
> [!NOTE]
|
|
507
|
+
> **Why `--no-prune` is rarely needed:**
|
|
508
|
+
> Automatic pruning is mathematically loss-free: Emit's unpushed edits safeguard strictly aborts the pull if an older article contains local modifications. You only need `--no-prune` in exceptional circumstances, such as temporarily suppressing Git working-tree changes during a quick pull.
|
|
509
|
+
|
|
510
|
+
**Example:**
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
# Pull the latest sliding window (prunes older cached files automatically)
|
|
514
|
+
emit pull
|
|
515
|
+
|
|
516
|
+
# Pull a specific legacy article by ID
|
|
517
|
+
emit pull 01M5XYZ9876543210123456789
|
|
518
|
+
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### `emit push`
|
|
524
|
+
|
|
525
|
+
Pushes local markdown article changes to the remote EmDash instance.
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
emit push [file] [options]
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
* `[file]`: Relative or absolute path to the Markdown file.
|
|
533
|
+
* `--allow-publish`: Required when promoting an article to `status: published`.
|
|
534
|
+
* `--all`: Pushes all modified files in the workspace.
|
|
535
|
+
* `-p, --profile <name>`: Explicitly target a specific connection profile.
|
|
536
|
+
|
|
537
|
+
**Example:**
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
# Push draft updates
|
|
541
|
+
emit push posts/draft/20261104_01M6...-my-article.md
|
|
542
|
+
|
|
543
|
+
# Publish live to remote
|
|
544
|
+
emit push posts/draft/20261104_01M6...-my-article.md --allow-publish
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
### `emit status`
|
|
551
|
+
|
|
552
|
+
Displays the local workspace synchronization status compared against the baseline cache.
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
emit status [-c, --collection <collection>] [-p, --profile <name>]
|
|
556
|
+
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Example Output:**
|
|
560
|
+
|
|
561
|
+
```text
|
|
562
|
+
--- Workspace Status (posts | origin: https://your-blog.example.com) ---
|
|
563
|
+
๐ข [synced ] published posts/published/20261015_01M5...-getting-started.md (Getting Started)
|
|
564
|
+
๐ก [modified] draft posts/draft/20261104_01M6...-ai-workflow-guide.md (AI Workflow Guide)
|
|
565
|
+
โช [untracked] draft posts/draft/20261104_01M6...-notes.md (Draft Notes)
|
|
566
|
+
|
|
567
|
+
Summary: 3 total (1 synced, 1 modified, 1 untracked)
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
### `emit diff`
|
|
574
|
+
|
|
575
|
+
Shows line-by-line differences between the working Markdown file and the baseline snapshot.
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
emit diff [filePath]
|
|
579
|
+
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### `emit pull-taxonomies`
|
|
585
|
+
|
|
586
|
+
Downloads categories and tags from EmDash and generates local YAML definitions under `categories/` and `tags/`.
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
emit pull-taxonomies
|
|
590
|
+
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**Output:**
|
|
594
|
+
|
|
595
|
+
```text
|
|
596
|
+
Pulling categories and tags from remote...
|
|
597
|
+
โ
Successfully synced taxonomies: 12 categories, 34 tags.
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
### `emit pull-media`
|
|
604
|
+
|
|
605
|
+
Downloads media assets and their companion metadata YAML files into `media/`.
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
emit pull-media [-l, --limit <n>] [--all] [--prune] [--metadata-only]
|
|
609
|
+
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
* `-l, --limit <n>`: Maximum number of media items to fetch (default: `300`).
|
|
613
|
+
* `--all`: Fetch all media items without limit.
|
|
614
|
+
* `--prune`: Remove local media files that no longer exist on remote.
|
|
615
|
+
* `--metadata-only`: Download metadata YAML files only, skipping large binary downloads.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
### `emit guide`
|
|
620
|
+
|
|
621
|
+
Prints the concise Golden Path cheat sheet and AI safety rules directly to the console.
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
emit guide
|
|
625
|
+
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
### `emit mcp`
|
|
631
|
+
|
|
632
|
+
Launches Emit as a Model Context Protocol (MCP) stdio server for AI agents.
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
emit mcp [-w, --workspace <path>]
|
|
636
|
+
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## ๐ค Model Context Protocol (MCP) Support
|
|
642
|
+
|
|
643
|
+
Emit features a first-class **Model Context Protocol (MCP)** server over stdio. This empowers AI assistantsโsuch as **Google Antigravity**, **Claude Code**, and **Cursor**โto autonomously manage, draft, review, and synchronize EmDash content.
|
|
644
|
+
|
|
645
|
+
### Setup Guide
|
|
646
|
+
|
|
647
|
+
#### 1. Claude Desktop (`claude_desktop_config.json`)
|
|
648
|
+
|
|
649
|
+
```json
|
|
650
|
+
{
|
|
651
|
+
"mcpServers": {
|
|
652
|
+
"emit": {
|
|
653
|
+
"command": "emit",
|
|
654
|
+
"args": ["mcp"]
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
#### 2. Cursor (`.cursor/mcp.json`)
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"mcpServers": {
|
|
666
|
+
"emit": {
|
|
667
|
+
"command": "npx",
|
|
668
|
+
"args": ["-y", "emdash-emit", "mcp"]
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
#### 3. Google Antigravity (`~/.gemini/antigravity/mcp_config.json`)
|
|
676
|
+
|
|
677
|
+
```json
|
|
678
|
+
{
|
|
679
|
+
"mcpServers": {
|
|
680
|
+
"emit": {
|
|
681
|
+
"command": "emit",
|
|
682
|
+
"args": ["mcp"]
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
> **Tip:** When profiles are registered using `emit profile add --workspace <dir>`, the MCP server automatically discovers credentials and paths without requiring secrets in your agent configuration.
|
|
690
|
+
|
|
691
|
+
### Available MCP Tools
|
|
692
|
+
|
|
693
|
+
| Tool | Purpose | Key Parameters |
|
|
694
|
+
| --- | --- | --- |
|
|
695
|
+
| `sync_status` | Inspect local sync state (`synced`, `modified`, `untracked`) | `collection` |
|
|
696
|
+
| `sync_pull` | Pull remote posts into local sliding window or pull by ID | `id`, `force`, `status`, `noPrune` (escape hatch) |
|
|
697
|
+
| `sync_new` | Scaffold new article with canonical ULID and frontmatter | `slug`, `title`, `locale`, `collection` |
|
|
698
|
+
| `sync_push` | Push local article changes to remote with preflight checks | `filePath`, `id`, `allowPublish` |
|
|
699
|
+
| `sync_diff` | Inspect diff between working file and baseline snapshot | `filePath` |
|
|
700
|
+
| `sync_pull_taxonomies` | Pull remote category and tag definitions into local YAML files | (none) |
|
|
701
|
+
| `sync_pull_media` | Pull remote media files and metadata into `media/` | `limit`, `all`, `prune`, `metadataOnly` |
|
|
702
|
+
| `profile_list` | List all configured connection profiles | (none) |
|
|
703
|
+
| `profile_switch` | Switch active profile for multi-site environments | `name` |
|
|
704
|
+
|
|
705
|
+
### Autonomous Agent Workflows
|
|
706
|
+
|
|
707
|
+
With Emit connected via MCP, you can give high-level natural language instructions to your AI:
|
|
708
|
+
|
|
709
|
+
* *"Check the workspace status, pull the latest posts, and show me what was changed recently."*
|
|
710
|
+
* *"Draft a new blog post titled 'Next-Gen Serverless Architectures' with slug `serverless-next`, add the `tech` category, and write a comprehensive outline."*
|
|
711
|
+
* *"Review the draft in `posts/draft/...`, proofread the content, and push it to remote as a draft."*
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## ๐ Distributed Concurrency & 3-Way Merge
|
|
716
|
+
|
|
717
|
+
### Distributed Mutex Lock Protocol
|
|
718
|
+
|
|
719
|
+
To eliminate race conditions when multiple users or agents edit the same post:
|
|
720
|
+
|
|
721
|
+
1. Emit issues a `POST /api/v1/content/lock` request to EmDash before pushing.
|
|
722
|
+
2. EmDash grants a 7-minute mutual exclusion lease.
|
|
723
|
+
3. If another author currently holds the lock, Emit aborts with an informative message indicating who owns the lock and when the lease expires.
|
|
724
|
+
4. Emit guarantees lock release using strict `try/finally` execution.
|
|
725
|
+
|
|
726
|
+
### Hybrid 3-Way Merge
|
|
727
|
+
|
|
728
|
+
If a remote article was updated after you pulled it:
|
|
729
|
+
|
|
730
|
+
1. Emit loads the **Base Snapshot** (the state when you last pulled), the **Local Working File**, and the **Remote Server State**.
|
|
731
|
+
2. **YAML Frontmatter**: Fields are merged key-by-key. Non-conflicting changes are applied automatically.
|
|
732
|
+
3. **Markdown Body**: Merged line-by-line using `node-diff3`.
|
|
733
|
+
4. If a conflict occurs, standard Git conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) are injected for human or agent resolution.
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## ๐ง Troubleshooting & FAQ
|
|
738
|
+
|
|
739
|
+
### Q: `SyncPruneConflictError`: Pull aborted due to unpushed modifications
|
|
740
|
+
|
|
741
|
+
```text
|
|
742
|
+
โ Cannot prune 1 local article(s) outside the sync window because they contain unpushed changes:
|
|
743
|
+
- posts/draft/20261001_01M4...-old-draft.md
|
|
744
|
+
Push your changes with 'emit push <file>' or discard them before pulling.
|
|
745
|
+
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Cause**: An article that has fallen outside the sliding window (e.g., beyond the latest 100 posts) contains local edits that have not been pushed to the remote server. To prevent accidental data loss, Emit aborts the pull.
|
|
749
|
+
|
|
750
|
+
**Solution**:
|
|
751
|
+
|
|
752
|
+
* If you want to keep the changes: Push them to remote using `emit push posts/draft/...`.
|
|
753
|
+
* If you want to discard local edits: Delete or revert the file, then re-run `emit pull`.
|
|
754
|
+
* If you want to temporarily skip pruning: Run `emit pull --no-prune`.
|
|
755
|
+
|
|
756
|
+
### Q: `PushValidationError`: Cannot publish article live without `--allow-publish`
|
|
757
|
+
|
|
758
|
+
**Cause**: Frontmatter has `status: published`, but `emit push` was executed without `--allow-publish`.
|
|
759
|
+
|
|
760
|
+
**Solution**: Run `emit push <filePath> --allow-publish`. This safety guardrail prevents accidental live publication.
|
|
761
|
+
|
|
762
|
+
### Q: Taxonomies not recognized during push
|
|
763
|
+
|
|
764
|
+
**Cause**: Categories or tags in frontmatter do not exist in the remote database.
|
|
765
|
+
|
|
766
|
+
**Solution**: Run `emit pull-taxonomies` to refresh your local category and tag list, and ensure frontmatter values match existing slugs.
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
## ๐งช Testing & Quality Assurance
|
|
771
|
+
|
|
772
|
+
Emit adheres to high code quality and test-driven development standards:
|
|
773
|
+
|
|
774
|
+
```bash
|
|
775
|
+
# Run unit test suite (Vitest)
|
|
776
|
+
npm test
|
|
777
|
+
|
|
778
|
+
# Run TypeScript strict typecheck
|
|
779
|
+
npm run typecheck
|
|
780
|
+
|
|
781
|
+
# Verify Prettier code style
|
|
782
|
+
npm run format:check
|
|
783
|
+
|
|
784
|
+
# Auto-format codebase
|
|
785
|
+
npm run format
|
|
786
|
+
|
|
787
|
+
# Run complete CI verification suite
|
|
788
|
+
npm run check
|
|
789
|
+
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
## ๐ License & Copyright
|
|
795
|
+
|
|
796
|
+
This project is open-source software licensed under the [MIT License](LICENSE).
|
|
797
|
+
|
|
798
|
+
Copyright (c) 2026 SPORT-SERVICE RSโ
R. All rights reserved.
|
|
2
799
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|