emdash-emit 0.1.8 → 0.1.10

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.
Files changed (148) hide show
  1. package/README.md +132 -28
  2. package/dist/bin/emit.d.ts.map +1 -1
  3. package/dist/bin/emit.js +6 -0
  4. package/dist/bin/emit.js.map +1 -1
  5. package/dist/cli/commands/conflicts.d.ts +4 -0
  6. package/dist/cli/commands/conflicts.d.ts.map +1 -0
  7. package/dist/cli/commands/conflicts.js +153 -0
  8. package/dist/cli/commands/conflicts.js.map +1 -0
  9. package/dist/cli/commands/delete.d.ts +4 -0
  10. package/dist/cli/commands/delete.d.ts.map +1 -0
  11. package/dist/cli/commands/delete.js +89 -0
  12. package/dist/cli/commands/delete.js.map +1 -0
  13. package/dist/cli/commands/doctor.d.ts +4 -0
  14. package/dist/cli/commands/doctor.d.ts.map +1 -0
  15. package/dist/cli/commands/doctor.js +82 -0
  16. package/dist/cli/commands/doctor.js.map +1 -0
  17. package/dist/cli/commands/new.d.ts.map +1 -1
  18. package/dist/cli/commands/new.js +52 -22
  19. package/dist/cli/commands/new.js.map +1 -1
  20. package/dist/cli/commands/profile.d.ts.map +1 -1
  21. package/dist/cli/commands/profile.js +67 -13
  22. package/dist/cli/commands/profile.js.map +1 -1
  23. package/dist/cli/commands/pull.d.ts +5 -0
  24. package/dist/cli/commands/pull.d.ts.map +1 -1
  25. package/dist/cli/commands/pull.js +119 -39
  26. package/dist/cli/commands/pull.js.map +1 -1
  27. package/dist/cli/commands/push.d.ts.map +1 -1
  28. package/dist/cli/commands/push.js +38 -1
  29. package/dist/cli/commands/push.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts +19 -0
  31. package/dist/cli/commands/status.d.ts.map +1 -1
  32. package/dist/cli/commands/status.js +87 -5
  33. package/dist/cli/commands/status.js.map +1 -1
  34. package/dist/cli/context.d.ts +38 -1
  35. package/dist/cli/context.d.ts.map +1 -1
  36. package/dist/cli/context.js +129 -16
  37. package/dist/cli/context.js.map +1 -1
  38. package/dist/client/auth.d.ts +1 -0
  39. package/dist/client/auth.d.ts.map +1 -1
  40. package/dist/client/auth.js +1 -0
  41. package/dist/client/auth.js.map +1 -1
  42. package/dist/client/http.d.ts.map +1 -1
  43. package/dist/client/http.js +27 -10
  44. package/dist/client/http.js.map +1 -1
  45. package/dist/db/cache.d.ts +3 -0
  46. package/dist/db/cache.d.ts.map +1 -1
  47. package/dist/db/cache.js +39 -0
  48. package/dist/db/cache.js.map +1 -1
  49. package/dist/mcp/context.d.ts +8 -0
  50. package/dist/mcp/context.d.ts.map +1 -1
  51. package/dist/mcp/server.d.ts +8 -0
  52. package/dist/mcp/server.d.ts.map +1 -1
  53. package/dist/mcp/server.js +91 -15
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/mcp/tool-errors.d.ts +16 -0
  56. package/dist/mcp/tool-errors.d.ts.map +1 -0
  57. package/dist/mcp/tool-errors.js +137 -0
  58. package/dist/mcp/tool-errors.js.map +1 -0
  59. package/dist/mcp/tools/profile.d.ts +9 -2
  60. package/dist/mcp/tools/profile.d.ts.map +1 -1
  61. package/dist/mcp/tools/profile.js +53 -36
  62. package/dist/mcp/tools/profile.js.map +1 -1
  63. package/dist/mcp/tools/sync-conflicts.d.ts +4 -0
  64. package/dist/mcp/tools/sync-conflicts.d.ts.map +1 -0
  65. package/dist/mcp/tools/sync-conflicts.js +100 -0
  66. package/dist/mcp/tools/sync-conflicts.js.map +1 -0
  67. package/dist/mcp/tools/sync-delete.d.ts +4 -0
  68. package/dist/mcp/tools/sync-delete.d.ts.map +1 -0
  69. package/dist/mcp/tools/sync-delete.js +77 -0
  70. package/dist/mcp/tools/sync-delete.js.map +1 -0
  71. package/dist/mcp/tools/sync-diff.d.ts.map +1 -1
  72. package/dist/mcp/tools/sync-diff.js +22 -8
  73. package/dist/mcp/tools/sync-diff.js.map +1 -1
  74. package/dist/mcp/tools/sync-doctor.d.ts +4 -0
  75. package/dist/mcp/tools/sync-doctor.d.ts.map +1 -0
  76. package/dist/mcp/tools/sync-doctor.js +57 -0
  77. package/dist/mcp/tools/sync-doctor.js.map +1 -0
  78. package/dist/mcp/tools/sync-new.d.ts.map +1 -1
  79. package/dist/mcp/tools/sync-new.js +49 -23
  80. package/dist/mcp/tools/sync-new.js.map +1 -1
  81. package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
  82. package/dist/mcp/tools/sync-pull.js +132 -73
  83. package/dist/mcp/tools/sync-pull.js.map +1 -1
  84. package/dist/mcp/tools/sync-push.d.ts.map +1 -1
  85. package/dist/mcp/tools/sync-push.js +39 -33
  86. package/dist/mcp/tools/sync-push.js.map +1 -1
  87. package/dist/mcp/tools/sync-status.d.ts.map +1 -1
  88. package/dist/mcp/tools/sync-status.js +103 -11
  89. package/dist/mcp/tools/sync-status.js.map +1 -1
  90. package/dist/sync/conflicts.d.ts +89 -0
  91. package/dist/sync/conflicts.d.ts.map +1 -0
  92. package/dist/sync/conflicts.js +374 -0
  93. package/dist/sync/conflicts.js.map +1 -0
  94. package/dist/sync/delete.d.ts +58 -0
  95. package/dist/sync/delete.d.ts.map +1 -0
  96. package/dist/sync/delete.js +221 -0
  97. package/dist/sync/delete.js.map +1 -0
  98. package/dist/sync/diff.d.ts.map +1 -1
  99. package/dist/sync/diff.js +6 -3
  100. package/dist/sync/diff.js.map +1 -1
  101. package/dist/sync/doctor.d.ts +31 -0
  102. package/dist/sync/doctor.d.ts.map +1 -0
  103. package/dist/sync/doctor.js +224 -0
  104. package/dist/sync/doctor.js.map +1 -0
  105. package/dist/sync/media.d.ts +22 -0
  106. package/dist/sync/media.d.ts.map +1 -1
  107. package/dist/sync/media.js +342 -94
  108. package/dist/sync/media.js.map +1 -1
  109. package/dist/sync/new.d.ts +31 -0
  110. package/dist/sync/new.d.ts.map +1 -1
  111. package/dist/sync/new.js +139 -2
  112. package/dist/sync/new.js.map +1 -1
  113. package/dist/sync/orchestrator.d.ts +6 -0
  114. package/dist/sync/orchestrator.d.ts.map +1 -1
  115. package/dist/sync/orchestrator.js +54 -27
  116. package/dist/sync/orchestrator.js.map +1 -1
  117. package/dist/sync/preflight.d.ts.map +1 -1
  118. package/dist/sync/preflight.js +29 -3
  119. package/dist/sync/preflight.js.map +1 -1
  120. package/dist/sync/pull.d.ts +51 -0
  121. package/dist/sync/pull.d.ts.map +1 -1
  122. package/dist/sync/pull.js +289 -58
  123. package/dist/sync/pull.js.map +1 -1
  124. package/dist/sync/push.d.ts +12 -0
  125. package/dist/sync/push.d.ts.map +1 -1
  126. package/dist/sync/push.js +69 -24
  127. package/dist/sync/push.js.map +1 -1
  128. package/dist/sync/status.d.ts +21 -1
  129. package/dist/sync/status.d.ts.map +1 -1
  130. package/dist/sync/status.js +66 -6
  131. package/dist/sync/status.js.map +1 -1
  132. package/dist/sync/taxonomies.d.ts +14 -5
  133. package/dist/sync/taxonomies.d.ts.map +1 -1
  134. package/dist/sync/taxonomies.js +142 -9
  135. package/dist/sync/taxonomies.js.map +1 -1
  136. package/dist/utils/config.d.ts +23 -0
  137. package/dist/utils/config.d.ts.map +1 -1
  138. package/dist/utils/config.js +41 -4
  139. package/dist/utils/config.js.map +1 -1
  140. package/dist/utils/frontmatter.d.ts +51 -2
  141. package/dist/utils/frontmatter.d.ts.map +1 -1
  142. package/dist/utils/frontmatter.js +234 -9
  143. package/dist/utils/frontmatter.js.map +1 -1
  144. package/dist/utils/profile.d.ts +3 -3
  145. package/dist/utils/profile.d.ts.map +1 -1
  146. package/dist/utils/profile.js +105 -8
  147. package/dist/utils/profile.js.map +1 -1
  148. package/package.json +1 -1
package/README.md CHANGED
@@ -32,13 +32,16 @@
32
32
  - [Media Management & Upload Workflow](#media-management--upload-workflow)
33
33
  - [🤖 The Golden Path for AI & Humans (4-Step Cycle)](#-the-golden-path-for-ai--humans-4-step-cycle)
34
34
  - [The 4-Step Authoring Cycle](#the-4-step-authoring-cycle)
35
- - [Preflight Safety Guardrails (G1–G11)](#preflight-safety-guardrails-g1g11)
35
+ - [Preflight Safety Guardrails (G1–G12)](#preflight-safety-guardrails-g1g12)
36
36
  - [🛠 CLI Command Reference](#-cli-command-reference)
37
37
  - [`emit init`](#emit-init)
38
38
  - [`emit profile`](#emit-profile)
39
39
  - [`emit new`](#emit-new)
40
40
  - [`emit pull`](#emit-pull)
41
41
  - [`emit push`](#emit-push)
42
+ - [`emit delete`](#emit-delete)
43
+ - [`emit conflicts`](#emit-conflicts)
44
+ - [`emit doctor`](#emit-doctor)
42
45
  - [`emit status`](#emit-status)
43
46
  - [`emit diff`](#emit-diff)
44
47
  - [`emit pull-taxonomies`](#emit-pull-taxonomies)
@@ -193,6 +196,9 @@ emit profile add my-blog \
193
196
  > [!NOTE]
194
197
  > Profiles are securely stored with `0600` permissions at `~/.config/emit/profiles.yaml` (on macOS / Linux) or `%USERPROFILE%\.config\emit\profiles.yaml` (on Windows).
195
198
 
199
+ > [!NOTE]
200
+ > **`emit profile add` updates in place.** Re-running it with only `--origin` (for example after rotating a domain) keeps the stored `--token` and the `--workspace` link that `emit init` created, and reports `Kept the existing token and workspace link`. Pass `--token`/`--workspace` explicitly to replace them; an empty `--token ''` clears the credential on purpose.
201
+
196
202
  ### 2. Initialize a Workspace
197
203
 
198
204
  Navigate to your local repository directory and initialize it:
@@ -202,7 +208,7 @@ cd /path/to/local/blog-repo
202
208
  emit init
203
209
  ```
204
210
 
205
- This creates the `.emit/` directory with a default `config.json` and initializes the local baseline cache SQLite database (`.emit/cache.db`).
211
+ This creates the `.emit/` directory with `config.json` (inheriting `origin` from your resolved profile when `--origin` is omitted, and automatically linking `workspace` back into `~/.config/emit/profiles.yaml` unless `--no-link` is passed) and initializes the local baseline cache SQLite database (`.emit/cache.db`).
206
212
 
207
213
  ### 3. Multi-Site Management (Directory-Driven & Zero Friction)
208
214
 
@@ -222,7 +228,7 @@ emit profile list
222
228
  ```
223
229
 
224
230
  **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`.
231
+ 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`. Directory comparison is symlink-safe (`fs.realpathSync`), so a workspace reached through a symlinked path (or a macOS `/tmp`-style alias) still resolves to the right profile.
226
232
  2. **Explicit Profile Flag**: You can explicitly specify `--profile <name>` (or pass `profile: "<name>"` in MCP) from any directory to override automatic detection.
227
233
  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
234
 
@@ -230,7 +236,7 @@ emit profile list
230
236
 
231
237
  Emit resolves authentication credentials using a strict 4-tier priority chain (`src/client/auth.ts`):
232
238
 
233
- 1. **Environment Variables (`EMDASH_TOKEN` or `EMDASH_API_TOKEN`, plus optional `EMDASH_ORIGIN`)**: Highest priority; ideal for CI/CD pipelines and one-off overrides.
239
+ 1. **Environment Variables (`EMDASH_TOKEN` or `EMDASH_API_TOKEN`)**: Highest priority; ideal for CI/CD pipelines and one-off overrides. `EMDASH_ORIGIN` (and `EMDASH_WORKSPACE`) are read here as well and documented under [Origin Resolution Precedence](#origin-resolution-precedence).
234
240
  2. **Site Profile Registry (`~/.config/emit/profiles.yaml`)**: Matched via `--profile <name>` (or MCP `profile`), current workspace directory auto-detection, or a single registered profile.
235
241
  3. **EmDash CLI Credentials (`~/.config/emdash/auth.json`)**: Fallback when no token is configured in the profile.
236
242
  4. **Emit Custom Credentials (`~/.config/emit/credentials.json`)**: Final fallback.
@@ -238,6 +244,19 @@ Emit resolves authentication credentials using a strict 4-tier priority chain (`
238
244
  **Automatic OAuth Token Refresh on HTTP 401 (`src/client/http.ts`)**:
239
245
  When credentials include a `refresh_token` (such as OAuth sessions in `~/.config/emdash/auth.json` or `~/.config/emit/credentials.json`) and a request returns `401 Unauthorized`, Emit automatically exchanges the `refresh_token` at `POST /_emdash/api/auth/token` (`grant_type: "refresh_token"`), persists the refreshed token back to `auth.json` when applicable, and transparently retries the request once.
240
246
 
247
+ #### Origin Resolution Precedence
248
+
249
+ The target instance (`origin`) is resolved by one shared function (`resolveOriginPriority()` in `src/cli/context.ts`, reused by the MCP server) so CLI and MCP always agree:
250
+
251
+ 1. **Explicitly selected profile** — `--profile <name>` (or MCP `profile: "<name>"`)
252
+ 2. **`EMDASH_ORIGIN`** — overrides an *implicitly* resolved profile so CI/staging overrides actually take effect
253
+ 3. **Auto-resolved profile** — current-directory match, unique origin match, or the single registered profile
254
+ 4. **Resolved auth origin** — carried by `~/.config/emdash/auth.json` / `~/.config/emit/credentials.json`
255
+ 5. **Workspace `.emit/config.json`** → then the `https://example.com` sentinel default
256
+
257
+ > [!NOTE]
258
+ > Every silent override is surfaced as a `⚠️ Warning` naming the **real source** of each value (`.emit/config.json` values are never mislabeled as `EMDASH_ORIGIN` or the reverse). When you select `--profile` explicitly, `EMDASH_ORIGIN` is ignored **and reported**. Comparisons ignore trailing slashes. `emit init` follows the same ranking (`--origin` > `EMDASH_ORIGIN` > auto-resolved profile) and refuses to link the workspace to a profile whose origin does not match the initialized origin.
259
+
241
260
  ### 5. Workspace Configuration (`.emit/config.json`)
242
261
 
243
262
  Running `emit init` generates `.emit/config.json` with the following keys (`src/utils/config.ts`):
@@ -255,10 +274,10 @@ Running `emit init` generates `.emit/config.json` with the following keys (`src/
255
274
 
256
275
  | Key | Default | Description |
257
276
  | --- | --- | --- |
258
- | `origin` | `"https://example.com"` | Base URL of the target EmDash instance (overridden by `EMDASH_ORIGIN` or active profile) |
277
+ | `origin` | `"https://example.com"` | Base URL of the target EmDash instance, exactly as written to disk. It is never rewritten in-memory by `EMDASH_ORIGIN`; see [Origin Resolution Precedence](#origin-resolution-precedence) for how the effective origin is chosen |
259
278
  | `defaultCollection` | `"posts"` | Default content collection when `--collection` is omitted |
260
279
  | `defaultLocale` | `"ja"` | Default locale used when scaffolding new drafts via `emit new` / `sync_new` |
261
- | `postsSyncLimit` | `100` | Sliding window size for batch article pulls (also supports legacy `syncLimit` fallback) |
280
+ | `postsSyncLimit` | `100` | Sliding window size for batch article pulls (when `status: all`, fetches up to `postsSyncLimit` `published` + up to `postsSyncLimit` `draft` articles in parallel; also supports legacy `syncLimit` fallback) |
262
281
  | `mediaSyncLimit` | `300` | Default maximum items fetched by `emit pull-media` / `sync_pull_media` |
263
282
  | `concurrency` | `8` | Parallel HTTP fetch concurrency during batch pulls |
264
283
 
@@ -320,8 +339,10 @@ your-blog/
320
339
  └── posts/ # Local Markdown Articles (<yyyyMMdd>_<ulid>-<title>.md)
321
340
  ├── draft/ # Draft articles (named after title; falls back to slug if empty)
322
341
  │ └── 20261104_01M6...-My-New-Post.md
323
- └── published/ # Live published articles
324
- └── 20261015_01M5...-Getting-Started.md
342
+ ├── published/ # Live published articles
343
+ │ └── 20261015_01M5...-Getting-Started.md
344
+ └── conflicts/ # Read-only rescue copies of divergent local content (written by pull; never pushed or pruned)
345
+ └── 20261101_01M5...-Getting-Started.conflict.md
325
346
 
326
347
  ```
327
348
 
@@ -333,7 +354,7 @@ Emit maintains a local SQLite database via Node.js built-in `node:sqlite` (`src/
333
354
  | --- | --- |
334
355
  | `entries` | Caches the latest remote JSON envelope and revision (`rev`) for each synced article |
335
356
  | `base_snapshots` | Stores the baseline Markdown body and baseline frontmatter at the last synced revision to power Hybrid 3-Way Merge (`frontmatter_yaml` holds a **JSON-encoded** `Frontmatter` object, not YAML text) |
336
- | `sync_state` | Tracks each article's workspace-relative file path and SHA-256 content hash to detect local modifications (`synced` vs `modified`) |
357
+ | `sync_state` | Tracks each article's workspace-relative file path and SHA-256 content hash to detect local modifications (`synced` vs `modified` vs `untracked`, plus `invalid` when YAML frontmatter fails parsing; `invalid` files are excluded from `emit push --all`, cached `invalid` files are automatically self-healed on batch `emit pull` / `sync_pull`, and uncached `invalid` files outside the sliding window can be repaired via `emit pull <ULID>` or `sync_pull(id: "<ULID>")`) |
337
358
  | `bylines` | Caches remote author records (`id`, `slug`, `display_name`, `is_guest`) synced automatically via `/_emdash/api/mcp` (`byline_list`) on pull, enabling slug ⇔ ULID resolution and default author selection |
338
359
  | `media_state` | Tracks offline draft image uploads (`local_hash`, `media_id`, `storage_key`, `url`, `workspace_path`, `status`: `pending` \| `uploaded` \| `ready` \| `failed`) for idempotency, retry recovery, and cross-article asset reuse |
339
360
 
@@ -487,9 +508,9 @@ Whether you are a human developer or an autonomous AI agent (Antigravity, Claude
487
508
  * To push a draft: `emit push posts/draft/<filename>.md`
488
509
  * To publish live: update `status: published` and run `emit push posts/draft/<filename>.md --allow-publish`.
489
510
 
490
- ### Preflight Safety Guardrails (G1–G11)
511
+ ### Preflight Safety Guardrails (G1–G14)
491
512
 
492
- To ensure autonomous AI agents and automated scripts cannot corrupt workspace state or accidentally publish unintended content, Emit enforces 11 strict preflight guardrails before executing any write operation:
513
+ To ensure autonomous AI agents and automated scripts cannot corrupt workspace state or accidentally publish unintended content, Emit enforces 14 strict preflight guardrails before executing any write operation:
493
514
 
494
515
  | Guard | Code Name | Description | Severity |
495
516
  | --- | --- | --- | --- |
@@ -504,6 +525,10 @@ To ensure autonomous AI agents and automated scripts cannot corrupt workspace st
504
525
  | **G9** | `MODIFIED_OPAQUE_BLOCK` | Verifies that `<!--ec:block ... -->` opaque fences have not been tampered with or corrupted | Error |
505
526
  | **G10** | `INVALID_FEATURED_IMAGE` | Verifies that `featuredImage` is a 26-character canonical media ULID (normalized to uppercase) or `null`. A remote media URL that contains a ULID is resolved with a warning; local file paths and malformed values are rejected | Error |
506
527
  | **G11** | `LOCAL_IMAGE_AUTO_UPLOAD` (`MISSING_LOCAL_IMAGE_FILE` / `INVALID_LOCAL_IMAGE_TYPE` / `LOCAL_IMAGE_SIZE_EXCEEDED` / `PROMOTED_MEDIA_NOT_FOUND_REMOTE` / `[MEDIA_EXISTENCE_UNVERIFIED]`) | Detects standalone `media/drafts/<file>` image lines (or reuses an already-promoted `media/` asset across articles), validates file existence, extension, and size (≤ 50 MiB), queues them for automatic upload, URL rewrite, and `media/` promotion during `push`, and verifies every reused asset against `GET /_emdash/api/media/{id}` before its URL is baked into the body | Info / Warning / Error |
528
+ | **G12** | `QUARANTINED_COPY_NOT_PUSHABLE` | Refuses to push a rescue copy under `posts/conflicts/` (written by a pull that found divergent local content) before any network request, because it would overwrite the canonical article of the same ULID with stale content and recreate duplicate local files | Error |
529
+ | **G13** | `CANONICAL_HAS_UNPUSHED_EDITS` | Protects canonical articles from being silently overwritten during conflict resolution (`accept-local`) when unpushed local modifications exist; requires explicit `--force` (`force: true`) | Error |
530
+ | **G14** | `QUARANTINED_PATH_REQUIRED` | Restricts conflict resolution targets strictly within `<collection>/conflicts/`, preventing accidental overwrites or unlinks outside the quarantine directory | Error |
531
+ | **Guard** | `DUPLICATE_SLUG_GUARD` | Scans local drafts, published posts, cache index, and remote collection during `emit new` / `sync_new` to strictly block creating scaffold articles with already-existing slugs | Error |
507
532
 
508
533
  ---
509
534
 
@@ -607,13 +632,13 @@ emit pull [id] [options]
607
632
  ```
608
633
 
609
634
  * `[id]`: Optional article ID or ULID. If specified, pulls only that specific article (even if outside the window). Taxonomies are not pulled in single-article mode.
610
- * `-s, --status <draft|published|all>`: Filter by publication status (default: `all`).
635
+ * `-s, --status <draft|published|all>`: Filter by publication status (default: `all`). When set to `all`, the sliding window (`postsSyncLimit`, default `100`) is applied **per status** (fetching up to `postsSyncLimit` `published` + up to `postsSyncLimit` `draft` articles in parallel).
611
636
  * `-c, --collection <collection>`: Collection name (default: `posts`).
612
637
  * `--no-prune`: Do not prune local articles that have fallen outside the sliding window (escape hatch; not intended for routine use).
613
638
  * `--force`: Force overwrite local files and bypass differential hash checking.
614
639
  * `--concurrency <n>`: Parallel processing task limit (default: `8`).
615
640
  * `--posts-only`: Skip automatic remote taxonomy sync. Pulls articles only (local taxonomy candidates hint is still displayed from cached definitions).
616
- * `--media`: Also pull media assets and companion metadata into `media/`.
641
+ * `--media`: Also pull media assets and companion metadata into `media/` (bounded by `mediaSyncLimit`, default `300`). Use `emit pull-media` for `--limit`, `--all`, and `--prune` control.
617
642
  * `--metadata-only`: With `--media`: download only metadata YAML, skip binary file downloads.
618
643
 
619
644
  > [!NOTE]
@@ -621,7 +646,10 @@ emit pull [id] [options]
621
646
 
622
647
  > [!NOTE]
623
648
  > **Why `--no-prune` is rarely needed:**
624
- > 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.
649
+ > Automatic pruning is mathematically loss-free: Emit's unpushed edits safeguard inspects both the canonical file and any duplicate filenames sharing the same 26-character ULID, strictly aborting the pull (`SyncPruneConflictError`) if any of them contain local modifications. Clean duplicate files are removed alongside the canonical file. You only need `--no-prune` in exceptional circumstances, such as temporarily suppressing Git working-tree changes during a quick pull.
650
+
651
+ > [!NOTE]
652
+ > **Unpushed writing is never discarded.** When a pull finds local content that diverges from the last synced baseline, Emit first moves that file to `posts/conflicts/<name>.conflict.md` (a read-only rescue copy) and then writes the canonical article. Rescue copies are never pushed or pruned automatically, and pushing one directly is refused with `QUARANTINED_COPY_NOT_PUSHABLE` (Guard **G12**): read the copy, merge any valuable content into the canonical article, then delete it. If an invalid file also carries local edits, `--force` is the escape hatch (it quarantines the divergent copy, then writes the repaired file).
625
653
 
626
654
  **Example:**
627
655
 
@@ -656,11 +684,11 @@ Available Categories:
656
684
  Available Tags:
657
685
  - ai, cloudflare, emit, typescript
658
686
 
659
- Fetching articles from remote (posts, status: all, window: 100, concurrency: 8)...
660
- [1/100] ✅ Pulled hello-world -> posts/published/20261101_01M5...-Hello-World.md
661
- [2/100] ✅ Pulled draft-post -> posts/draft/20261104_01M6...-Draft-Post.md
687
+ Fetching articles from remote (posts, status: all, window: 100/status, concurrency: 8)...
688
+ [1/169] ✅ Pulled hello-world -> posts/published/20261101_01M5...-Hello-World.md
689
+ [2/169] ✅ Pulled draft-post -> posts/draft/20261104_01M6...-Draft-Post.md
662
690
 
663
- 🎉 Articles: 100 total (12 updated, 88 skipped/up-to-date)
691
+ 🎉 Articles: 169 total [100 published, 69 draft] (12 updated, 157 skipped/up-to-date)
664
692
 
665
693
  ```
666
694
 
@@ -693,6 +721,66 @@ emit push posts/draft/20261104_01M6...-my-article.md --allow-publish
693
721
 
694
722
  ---
695
723
 
724
+ ### `emit delete`
725
+
726
+ Safely deletes a local Markdown article and its remote counterpart on EmDash.
727
+
728
+ ```bash
729
+ emit delete <filePathOrId> [--allow-delete-published] [--dry-run] [-y, --yes] [-p, --profile <name>]
730
+ ```
731
+
732
+ * `<filePathOrId>`: Relative/absolute path to the Markdown file, or 26-character article ULID.
733
+ * `--allow-delete-published`: Required when deleting a published article (safety guardrail).
734
+ * `--dry-run`: Previews the deletion, inspecting referenced media and lines without modifying disk or remote.
735
+ * `-y, --yes`: Skips interactive confirmation prompt.
736
+ * `-p, --profile <name>`: Explicitly target a specific connection profile.
737
+
738
+ > [!NOTE]
739
+ > **Rescue Backups on Deletion**: When an article is deleted, Emit automatically creates a safety rescue backup at `posts/conflicts/<name>.deleted.md` before removing the file, preserving your work in case of accidental deletion.
740
+
741
+ ---
742
+
743
+ ### `emit conflicts`
744
+
745
+ Inspects, diffs, and resolves quarantined sync conflict copies (`.conflict.md`) and deletion rescue backups (`.deleted.md`) residing in `posts/conflicts/`.
746
+
747
+ ```bash
748
+ # List all quarantined copies with status labels and canonical file paths
749
+ emit conflicts list [-c, --collection <collection>]
750
+
751
+ # View line-by-line diff between quarantined copy and canonical article
752
+ emit conflicts diff <filePath> [-c, --collection <collection>]
753
+
754
+ # Resolve conflict with a specific strategy
755
+ emit conflicts resolve <filePath> --accept-local [-f, --force] [-c, --collection <collection>]
756
+ emit conflicts resolve <filePath> --accept-canonical [-c, --collection <collection>]
757
+ ```
758
+
759
+ * `list`: Scans `<collection>/conflicts/`, classifying items as `⚠️ SYNC CONFLICT` or `🗑️ DELETED RESCUE`, showing sizes and dates.
760
+ * `diff`: Displays a unified diff against the current canonical article.
761
+ * `resolve --accept-local`: Restores the quarantined content to the canonical location and purges the quarantine file. Guard **G13** blocks overwriting canonical articles that contain unpushed local modifications unless `-f, --force` is explicitly provided.
762
+ * `resolve --accept-canonical`: Safely discards the quarantined copy, retaining the canonical version.
763
+
764
+ ---
765
+
766
+ ### `emit doctor`
767
+
768
+ Runs an automated diagnostic health check across the local workspace and cache database.
769
+
770
+ ```bash
771
+ emit doctor [-c, --collection <collection>] [-p, --profile <name>]
772
+ ```
773
+
774
+ Validates 6 critical health vectors:
775
+ 1. **Version Compatibility**: Checks tool version against `createdWithVersion` / `lastSyncedVersion` in config.
776
+ 2. **Origin Consistency**: Verifies `.emit/config.json`, profile origin, and environment variables.
777
+ 3. **Frontmatter Integrity**: Ensures all local Markdown files have valid parseable frontmatter.
778
+ 4. **Conflicts & Quarantine**: Flags unresolved conflict copies and deletion backups in `conflicts/`.
779
+ 5. **Sync Status**: Verifies local baseline hash tracking.
780
+ 6. **Referenced Media Existence**: Validates that all local media referenced in article Markdown bodies exist on disk.
781
+
782
+ ---
783
+
696
784
  ### `emit status`
697
785
 
698
786
  Displays the local workspace synchronization status compared against the baseline cache.
@@ -709,13 +797,17 @@ emit status [-c, --collection <collection>] [-p, --profile <name>]
709
797
  🟡 [modified] draft posts/draft/20261104_01M6BBB...-AI-Workflow-Guide.md (AI Workflow Guide)
710
798
  ⚪ [untracked] draft posts/draft/20261104_01M6AAA...-Draft-Notes.md (Draft Notes)
711
799
  🟢 [synced ] published posts/published/20261015_01M5ZZZ...-Getting-Started.md (Getting Started)
800
+ 🔴 [invalid ] published posts/published/20190405_01M6CCC...-Broken-Post.md (Broken-Post) [uncached] — bad indentation of a mapping entry at line 5
712
801
 
713
- Summary: 3 total (1 synced, 1 modified, 1 untracked)
802
+ Summary: 4 total (1 synced, 1 modified, 1 untracked, 1 invalid)
714
803
  ```
715
804
 
716
805
  > [!NOTE]
717
806
  > Articles are sorted newest-first by date prefix and ULID. The date prefix reflects the article's publication or creation date, not the last-modified time. To locate currently active edits, look for files marked `[modified]`.
718
807
 
808
+ > [!NOTE]
809
+ > `[invalid]` marks a file whose YAML frontmatter could not be parsed (usually an older serializer wrote unquoted percent-encoded taxonomy slugs). Batch `emit pull` self-heals cached invalid files; files outside the sliding window are marked `[uncached]` and are repaired with `emit pull <ULID>` (MCP: `sync_pull` with `id: "<ULID>"`). When `<collection>/conflicts/` holds rescue copies, the count is reported separately (`conflicts`) and never counted as an article.
810
+
719
811
  ---
720
812
 
721
813
  ### `emit diff`
@@ -735,7 +827,7 @@ emit diff [filePath] [-i, --id <id>] [-p, --profile <name>]
735
827
 
736
828
  ### `emit pull-taxonomies`
737
829
 
738
- Downloads categories and tags from EmDash and generates local YAML definitions under `categories/` and `tags/`. This is also performed automatically by `emit pull` in batch mode.
830
+ Downloads categories and tags from EmDash and generates local YAML definitions under `categories/` and `tags/`. This is also performed automatically by `emit pull` in batch mode. If one taxonomy endpoint fails, existing local definitions for that taxonomy are preserved with a warning; if both endpoints fail, the command exits with code `1`.
739
831
 
740
832
  ```bash
741
833
  emit pull-taxonomies
@@ -761,13 +853,13 @@ Available Tags:
761
853
 
762
854
  ### `emit pull-media`
763
855
 
764
- Downloads media assets and their companion metadata YAML files into `media/`.
856
+ Downloads media assets and their companion metadata YAML files into `media/`. Supports pulling all media, a limited batch, or targeted media referenced by a specific article.
765
857
 
766
858
  ```bash
767
- emit pull-media [-l, --limit <n>] [--all] [--prune] [--metadata-only]
768
-
859
+ emit pull-media [-a, --article <id-or-path>] [-l, --limit <n>] [--all] [--prune] [--metadata-only]
769
860
  ```
770
861
 
862
+ * `-a, --article <id-or-path>`: Pulls only media assets referenced by the specified article (featured image and body images), saving bandwidth and disk space.
771
863
  * `-l, --limit <n>`: Maximum number of media items to fetch (default: `300`).
772
864
  * `--all`: Fetch all media items without limit.
773
865
  * `--prune`: Remove local media files that no longer exist on remote.
@@ -851,16 +943,22 @@ Emit features a first-class **Model Context Protocol (MCP)** server over stdio.
851
943
 
852
944
  | Tool | Purpose | Key Parameters |
853
945
  | --- | --- | --- |
854
- | `sync_status` | Inspect local sync state (articles returned newest-first) | `collection`, `profile` |
855
- | `sync_pull` | Pull remote articles **and taxonomies** into local workspace (includes taxonomy hints) | `collection`, `id`, `filePath`, `status`, `force`, `noPrune`, `prune`, `postsOnly`, `includeMedia`, `metadataOnly`, `profile` |
856
- | `sync_new` | Scaffold new article with canonical ULID, frontmatter, default byline, and taxonomy hints | `slug` (required), `title`, `collection`, `locale`, `byline`, `profile` |
857
- | `sync_push` | Push local article changes to remote with preflight checks and `media/drafts/*` auto-upload | `filePath` (required), `allowPublish`, `profile` |
946
+ | `sync_status` | Inspect local sync state (summary-first structure with capped actionRequired, articles, and search filters) | `collection`, `status`, `search`, `profile` |
947
+ | `sync_pull` | Pull remote articles **and taxonomies** into local workspace (includes taxonomy hints; preserves divergent local copies under `posts/conflicts/`) | `collection`, `id`, `filePath`, `status`, `force`, `noPrune`, `prune`, `postsOnly`, `includeMedia`, `metadataOnly`, `profile` |
948
+ | `sync_new` | Scaffold new article with canonical ULID, frontmatter, default byline, duplicate slug guard, and taxonomy hints | `slug` (required), `title`, `collection`, `locale`, `byline`, `profile` |
949
+ | `sync_push` | Push local article changes to remote with preflight checks and `media/drafts/*` auto-upload; refuses `posts/conflicts/` rescue copies (G12) | `filePath` (required), `allowPublish`, `profile` |
950
+ | `sync_delete` | Safely delete article locally and remotely with rescue backup (`.deleted.md`) and unpushed edit protection | `filePath`, `id`, `allowDeletePublished`, `dryRun`, `profile` |
951
+ | `sync_conflicts` | List, diff, and resolve quarantined conflict copies and deletion backups in `posts/conflicts/` (G13 unpushed edit guard with `force`) | `action` ("list"\|"diff"\|"resolve"), `filePath`, `strategy` ("accept-local"\|"accept-canonical"), `force`, `profile` |
952
+ | `sync_doctor` | Run comprehensive workspace diagnostics (version, origin, frontmatter, conflicts, sync, referenced media) | `collection`, `profile` |
858
953
  | `sync_diff` | Inspect diff between working file and baseline snapshot | `filePath`, `id`, `profile` |
859
954
  | `sync_pull_taxonomies` | Pull remote category and tag definitions (standalone; returns taxonomy hints) | `profile` |
860
- | `sync_pull_media` | Pull remote media files and metadata into `media/` | `limit`, `all`, `downloadFiles`, `profile` |
955
+ | `sync_pull_media` | Pull remote media files and metadata into `media/` (supports batch or targeted article media) | `article`, `limit`, `all`, `downloadFiles`, `profile` |
861
956
  | `profile_list` | List all configured connection profiles | (none) |
862
957
  | `profile_switch` | Switch active profile for multi-site environments | `name` (required), `persist` |
863
958
 
959
+ > [!NOTE]
960
+ > **Uniform `_meta` Context & Structured Diagnostics**: Every MCP tool response (success and error) includes structured `_meta` context containing `profile`, `workspace`, `origin`, and `resolvedBy` resolution rationale. Handlers return structured error payloads with actionable suggestions, HTTP status, and preflight codes rather than uncaught raw exceptions.
961
+
864
962
  ### Autonomous Agent Workflows
865
963
 
866
964
  With Emit connected via MCP, you can give high-level natural language instructions to your AI:
@@ -924,6 +1022,12 @@ Push your changes with 'emit push <file>' or discard them before pulling.
924
1022
 
925
1023
  **Solution**: Run `emit pull` or `sync_pull` (taxonomies are synced automatically), or use `emit pull-taxonomies` standalone to refresh your local category and tag list. Preflight Guard G4 will guide you with valid options.
926
1024
 
1025
+ ### Q: `[NETWORK_ERROR]`: Network request failed (`fetch failed`)
1026
+
1027
+ **Cause**: The configured origin URL is unreachable due to DNS resolution failure, offline network/VPN state, or an incorrect origin in `~/.config/emit/profiles.yaml` or `.emit/config.json`.
1028
+
1029
+ **Solution**: Verify the `origin` URL reported in `Endpoint:` and check `emit profile list` / `.emit/config.json`. If `.emit/config.json` and your active profile point to different origins, Emit prints an origin mismatch warning to help you reconcile them.
1030
+
927
1031
  ---
928
1032
 
929
1033
  ## 🧪 Testing & Quality Assurance
@@ -1 +1 @@
1
- {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/bin/emit.ts"],"names":[],"mappings":";AAwBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,OAAO,SAAgB,CAAC;AAyBrC,wBAAgB,MAAM,CAAC,IAAI,WAAe,GAAG,IAAI,CAEhD"}
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/bin/emit.ts"],"names":[],"mappings":";AAwBA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,eAAO,MAAM,OAAO,SAAgB,CAAC;AA4BrC,wBAAgB,MAAM,CAAC,IAAI,WAAe,GAAG,IAAI,CAEhD"}
package/dist/bin/emit.js CHANGED
@@ -28,6 +28,9 @@ import { registerNewCommand } from '../cli/commands/new.js';
28
28
  import { registerPullCommands } from '../cli/commands/pull.js';
29
29
  import { registerPushCommand } from '../cli/commands/push.js';
30
30
  import { registerStatusInitCommands } from '../cli/commands/status.js';
31
+ import { registerDeleteCommand } from '../cli/commands/delete.js';
32
+ import { registerConflictsCommand } from '../cli/commands/conflicts.js';
33
+ import { registerDoctorCommand } from '../cli/commands/doctor.js';
31
34
  import { getPackageVersion } from '../utils/version.js';
32
35
  export const program = new Command();
33
36
  program
@@ -41,6 +44,9 @@ registerNewCommand(program, resolveContext);
41
44
  registerPullCommands(program, resolveContext);
42
45
  registerPushCommand(program, resolveContext);
43
46
  registerStatusInitCommands(program, resolveContext);
47
+ registerDeleteCommand(program, resolveContext);
48
+ registerConflictsCommand(program, resolveContext);
49
+ registerDoctorCommand(program, resolveContext);
44
50
  // emit mcp
45
51
  program
46
52
  .command('mcp')
@@ -1 +1 @@
1
- {"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/bin/emit.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAErC,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,2EAA2E,CAAC;KACxF,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAEhC,MAAM,cAAc,GAAG,wBAAwB,EAAE,CAAC;AAElD,4BAA4B;AAC5B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,kBAAkB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC5C,oBAAoB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC9C,mBAAmB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC7C,0BAA0B,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAEpD,WAAW;AACX,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,0EAA0E,CAAC;KACvF,MAAM,CAAC,wBAAwB,EAAE,6BAA6B,CAAC;KAC/D,MAAM,CAAC,KAAK,EAAE,OAA+B,EAAE,EAAE;IAChD,MAAM,YAAY,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEL,MAAM,UAAU,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI;IACxC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,4CAA4C;AAC5C,MAAM,iBAAiB,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAEtC,IAAI,iBAAiB,EAAE,CAAC;IACtB,MAAM,EAAE,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/bin/emit.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAErC,OAAO;KACJ,IAAI,CAAC,MAAM,CAAC;KACZ,WAAW,CAAC,2EAA2E,CAAC;KACxF,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAEhC,MAAM,cAAc,GAAG,wBAAwB,EAAE,CAAC;AAElD,4BAA4B;AAC5B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,kBAAkB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC5C,oBAAoB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC9C,mBAAmB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC7C,0BAA0B,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AACpD,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAC/C,wBAAwB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAClD,qBAAqB,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAE/C,WAAW;AACX,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,0EAA0E,CAAC;KACvF,MAAM,CAAC,wBAAwB,EAAE,6BAA6B,CAAC;KAC/D,MAAM,CAAC,KAAK,EAAE,OAA+B,EAAE,EAAE;IAChD,MAAM,YAAY,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC;AAEL,MAAM,UAAU,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI;IACxC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,4CAA4C;AAC5C,MAAM,iBAAiB,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAEtC,IAAI,iBAAiB,EAAE,CAAC;IACtB,MAAM,EAAE,CAAC;AACX,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContextResolver } from '../context.js';
3
+ export declare function registerConflictsCommand(program: Command, resolveContext: CliContextResolver): void;
4
+ //# sourceMappingURL=conflicts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflicts.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/conflicts.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,kBAAkB,GACjC,IAAI,CAqJN"}
@@ -0,0 +1,153 @@
1
+ /*
2
+ * Copyright (C) 2026 SPORT-SERVICE RS★R
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ import { ApiError } from '../../client/error.js';
23
+ import { listConflicts, diffConflict, resolveConflict, CanonicalHasUnpushedEditsError, QuarantinedPathRequiredError, } from '../../sync/conflicts.js';
24
+ export function registerConflictsCommand(program, resolveContext) {
25
+ const reportActionFailure = (error) => {
26
+ if (error instanceof CanonicalHasUnpushedEditsError) {
27
+ console.error(`❌ ${error.message}`);
28
+ console.error(`💡 Suggestion: ${error.suggestion}`);
29
+ }
30
+ else if (error instanceof QuarantinedPathRequiredError) {
31
+ console.error(`❌ Error: [QUARANTINED_PATH_REQUIRED] ${error.message}`);
32
+ console.error(`💡 Suggestion: ${error.suggestion}`);
33
+ }
34
+ else if (error instanceof ApiError) {
35
+ console.error(error.toActionableString());
36
+ }
37
+ else if (error instanceof Error) {
38
+ console.error(`❌ Error: ${error.message}`);
39
+ }
40
+ else {
41
+ console.error(`❌ Error: ${String(error)}`);
42
+ }
43
+ process.exit(1);
44
+ };
45
+ const conflicts = program
46
+ .command('conflicts')
47
+ .description('Manage quarantined sync conflict copies and deletion rescue backups in posts/conflicts/');
48
+ // emit conflicts list
49
+ conflicts
50
+ .command('list')
51
+ .description('List all quarantined conflict and deleted backup files')
52
+ .option('-c, --collection <collection>', 'Target collection (defaults to "posts")')
53
+ .option('-p, --profile <name>', 'Explicitly use a specific connection profile')
54
+ .action(async (options) => {
55
+ try {
56
+ const ctx = resolveContext(options.profile, { optionalAuth: true });
57
+ const collection = options.collection || ctx.config.defaultCollection || 'posts';
58
+ const items = listConflicts(ctx.root, collection);
59
+ if (items.length === 0) {
60
+ console.log(`✅ No conflicts or rescue backups found in ${collection}/conflicts/.`);
61
+ return;
62
+ }
63
+ console.log(`📁 Quarantined Files in ${collection}/conflicts/ (${items.length} total):\n`);
64
+ for (const item of items) {
65
+ const typeLabel = item.type === 'conflict'
66
+ ? '⚠️ [SYNC CONFLICT]'
67
+ : item.type === 'deleted'
68
+ ? '🗑️ [DELETED RESCUE]'
69
+ : '❓ [UNKNOWN]';
70
+ const titleStr = item.title ? ` - "${item.title}"` : '';
71
+ const canonicalStr = item.canonicalPath
72
+ ? ` (canonical: ${item.canonicalPath})`
73
+ : ' (canonical: absent)';
74
+ console.log(` ${typeLabel} ${item.filePath}${titleStr}${canonicalStr}`);
75
+ console.log(` ID: ${item.id || 'unknown'} | Slug: ${item.slug || 'unknown'} | Size: ${item.sizeBytes} B | Created: ${item.createdAt}\n`);
76
+ }
77
+ console.log('Commands:');
78
+ console.log(' emit conflicts diff <filePath> Inspect differences');
79
+ console.log(' emit conflicts resolve <filePath> --accept-local Apply quarantined copy to canonical');
80
+ console.log(' emit conflicts resolve <filePath> --accept-canonical Discard quarantined copy\n');
81
+ }
82
+ catch (err) {
83
+ reportActionFailure(err);
84
+ }
85
+ });
86
+ // emit conflicts diff <filePath>
87
+ conflicts
88
+ .command('diff <filePath>')
89
+ .description('Inspect differences between a quarantined copy and the canonical article')
90
+ .option('-c, --collection <collection>', 'Target collection (defaults to "posts")')
91
+ .option('-p, --profile <name>', 'Explicitly use a specific connection profile')
92
+ .action(async (filePath, options) => {
93
+ try {
94
+ const ctx = resolveContext(options.profile, { optionalAuth: true });
95
+ const collection = options.collection || ctx.config.defaultCollection || 'posts';
96
+ const result = diffConflict({
97
+ workspaceRoot: ctx.root,
98
+ filePath,
99
+ collection,
100
+ });
101
+ console.log(`🔍 Diff for ${result.conflictFilePath} (${result.type}):`);
102
+ if (result.canonicalFilePath) {
103
+ console.log(` Canonical: ${result.canonicalFilePath}`);
104
+ console.log(` Changes: +${result.additions} / -${result.deletions} lines\n`);
105
+ }
106
+ else {
107
+ console.log(` Canonical: None (article was deleted or absent)\n`);
108
+ }
109
+ console.log(result.diffText);
110
+ }
111
+ catch (err) {
112
+ reportActionFailure(err);
113
+ }
114
+ });
115
+ // emit conflicts resolve <filePath>
116
+ conflicts
117
+ .command('resolve <filePath>')
118
+ .description('Resolve a quarantined conflict by accepting the local/quarantined copy or the canonical version')
119
+ .option('--accept-local', 'Replace canonical article with quarantined copy and delete quarantine')
120
+ .option('--accept-canonical', 'Discard the quarantined copy and keep canonical article')
121
+ .option('-f, --force', 'Force overwrite canonical article even if it has unpushed local modifications')
122
+ .option('-c, --collection <collection>', 'Target collection (defaults to "posts")')
123
+ .option('-p, --profile <name>', 'Explicitly use a specific connection profile')
124
+ .action(async (filePath, options) => {
125
+ try {
126
+ if (!options.acceptLocal && !options.acceptCanonical) {
127
+ console.error('❌ Error: Must specify either --accept-local or --accept-canonical');
128
+ process.exit(1);
129
+ }
130
+ const strategy = options.acceptLocal ? 'accept-local' : 'accept-canonical';
131
+ const ctx = resolveContext(options.profile, { optionalAuth: true });
132
+ const collection = options.collection || ctx.config.defaultCollection || 'posts';
133
+ const result = resolveConflict({
134
+ workspaceRoot: ctx.root,
135
+ cache: ctx.cache,
136
+ filePath,
137
+ strategy,
138
+ force: options.force,
139
+ collection,
140
+ });
141
+ console.log(`✅ ${result.message}`);
142
+ if (result.warnings && result.warnings.length > 0) {
143
+ for (const w of result.warnings) {
144
+ console.log(`⚠️ Warning: ${w}`);
145
+ }
146
+ }
147
+ }
148
+ catch (err) {
149
+ reportActionFailure(err);
150
+ }
151
+ });
152
+ }
153
+ //# sourceMappingURL=conflicts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflicts.js","sourceRoot":"","sources":["../../../src/cli/commands/conflicts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { Command } from 'commander';
2
+ import type { CliContextResolver } from '../context.js';
3
+ export declare function registerDeleteCommand(program: Command, resolveContext: CliContextResolver): void;
4
+ //# sourceMappingURL=delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/delete.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAkEhG"}