emdash-emit 0.1.9 → 0.1.11

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 (138) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +102 -13
  3. package/dist/bin/emit.d.ts.map +1 -1
  4. package/dist/bin/emit.js +6 -0
  5. package/dist/bin/emit.js.map +1 -1
  6. package/dist/cli/commands/conflicts.d.ts +4 -0
  7. package/dist/cli/commands/conflicts.d.ts.map +1 -0
  8. package/dist/cli/commands/conflicts.js +153 -0
  9. package/dist/cli/commands/conflicts.js.map +1 -0
  10. package/dist/cli/commands/delete.d.ts +4 -0
  11. package/dist/cli/commands/delete.d.ts.map +1 -0
  12. package/dist/cli/commands/delete.js +89 -0
  13. package/dist/cli/commands/delete.js.map +1 -0
  14. package/dist/cli/commands/doctor.d.ts +4 -0
  15. package/dist/cli/commands/doctor.d.ts.map +1 -0
  16. package/dist/cli/commands/doctor.js +82 -0
  17. package/dist/cli/commands/doctor.js.map +1 -0
  18. package/dist/cli/commands/new.d.ts.map +1 -1
  19. package/dist/cli/commands/new.js +52 -22
  20. package/dist/cli/commands/new.js.map +1 -1
  21. package/dist/cli/commands/pull.d.ts.map +1 -1
  22. package/dist/cli/commands/pull.js +23 -0
  23. package/dist/cli/commands/pull.js.map +1 -1
  24. package/dist/cli/commands/push.d.ts.map +1 -1
  25. package/dist/cli/commands/push.js +36 -1
  26. package/dist/cli/commands/push.js.map +1 -1
  27. package/dist/cli/commands/status.d.ts.map +1 -1
  28. package/dist/cli/commands/status.js +27 -7
  29. package/dist/cli/commands/status.js.map +1 -1
  30. package/dist/cli/context.d.ts +3 -1
  31. package/dist/cli/context.d.ts.map +1 -1
  32. package/dist/cli/context.js +17 -6
  33. package/dist/cli/context.js.map +1 -1
  34. package/dist/db/cache.d.ts +3 -0
  35. package/dist/db/cache.d.ts.map +1 -1
  36. package/dist/db/cache.js +39 -0
  37. package/dist/db/cache.js.map +1 -1
  38. package/dist/mcp/context.d.ts +7 -0
  39. package/dist/mcp/context.d.ts.map +1 -1
  40. package/dist/mcp/server.d.ts.map +1 -1
  41. package/dist/mcp/server.js +39 -2
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/mcp/tool-errors.d.ts +7 -2
  44. package/dist/mcp/tool-errors.d.ts.map +1 -1
  45. package/dist/mcp/tool-errors.js +80 -5
  46. package/dist/mcp/tool-errors.js.map +1 -1
  47. package/dist/mcp/tools/profile.d.ts.map +1 -1
  48. package/dist/mcp/tools/profile.js +4 -0
  49. package/dist/mcp/tools/profile.js.map +1 -1
  50. package/dist/mcp/tools/sync-conflicts.d.ts +4 -0
  51. package/dist/mcp/tools/sync-conflicts.d.ts.map +1 -0
  52. package/dist/mcp/tools/sync-conflicts.js +100 -0
  53. package/dist/mcp/tools/sync-conflicts.js.map +1 -0
  54. package/dist/mcp/tools/sync-delete.d.ts +4 -0
  55. package/dist/mcp/tools/sync-delete.d.ts.map +1 -0
  56. package/dist/mcp/tools/sync-delete.js +77 -0
  57. package/dist/mcp/tools/sync-delete.js.map +1 -0
  58. package/dist/mcp/tools/sync-diff.d.ts.map +1 -1
  59. package/dist/mcp/tools/sync-diff.js +3 -2
  60. package/dist/mcp/tools/sync-diff.js.map +1 -1
  61. package/dist/mcp/tools/sync-doctor.d.ts +4 -0
  62. package/dist/mcp/tools/sync-doctor.d.ts.map +1 -0
  63. package/dist/mcp/tools/sync-doctor.js +57 -0
  64. package/dist/mcp/tools/sync-doctor.js.map +1 -0
  65. package/dist/mcp/tools/sync-new.d.ts.map +1 -1
  66. package/dist/mcp/tools/sync-new.js +16 -3
  67. package/dist/mcp/tools/sync-new.js.map +1 -1
  68. package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
  69. package/dist/mcp/tools/sync-pull.js +41 -11
  70. package/dist/mcp/tools/sync-pull.js.map +1 -1
  71. package/dist/mcp/tools/sync-push.d.ts.map +1 -1
  72. package/dist/mcp/tools/sync-push.js +16 -2
  73. package/dist/mcp/tools/sync-push.js.map +1 -1
  74. package/dist/mcp/tools/sync-status.d.ts.map +1 -1
  75. package/dist/mcp/tools/sync-status.js +63 -22
  76. package/dist/mcp/tools/sync-status.js.map +1 -1
  77. package/dist/sync/conflicts.d.ts +89 -0
  78. package/dist/sync/conflicts.d.ts.map +1 -0
  79. package/dist/sync/conflicts.js +374 -0
  80. package/dist/sync/conflicts.js.map +1 -0
  81. package/dist/sync/delete.d.ts +58 -0
  82. package/dist/sync/delete.d.ts.map +1 -0
  83. package/dist/sync/delete.js +221 -0
  84. package/dist/sync/delete.js.map +1 -0
  85. package/dist/sync/diff.d.ts.map +1 -1
  86. package/dist/sync/diff.js +6 -3
  87. package/dist/sync/diff.js.map +1 -1
  88. package/dist/sync/doctor.d.ts +31 -0
  89. package/dist/sync/doctor.d.ts.map +1 -0
  90. package/dist/sync/doctor.js +273 -0
  91. package/dist/sync/doctor.js.map +1 -0
  92. package/dist/sync/media.d.ts +22 -0
  93. package/dist/sync/media.d.ts.map +1 -1
  94. package/dist/sync/media.js +342 -94
  95. package/dist/sync/media.js.map +1 -1
  96. package/dist/sync/merge.d.ts +17 -1
  97. package/dist/sync/merge.d.ts.map +1 -1
  98. package/dist/sync/merge.js +110 -3
  99. package/dist/sync/merge.js.map +1 -1
  100. package/dist/sync/new.d.ts +31 -0
  101. package/dist/sync/new.d.ts.map +1 -1
  102. package/dist/sync/new.js +139 -2
  103. package/dist/sync/new.js.map +1 -1
  104. package/dist/sync/orchestrator.d.ts.map +1 -1
  105. package/dist/sync/orchestrator.js +2 -0
  106. package/dist/sync/orchestrator.js.map +1 -1
  107. package/dist/sync/preflight.d.ts.map +1 -1
  108. package/dist/sync/preflight.js +29 -3
  109. package/dist/sync/preflight.js.map +1 -1
  110. package/dist/sync/pull.d.ts +4 -0
  111. package/dist/sync/pull.d.ts.map +1 -1
  112. package/dist/sync/pull.js +159 -80
  113. package/dist/sync/pull.js.map +1 -1
  114. package/dist/sync/push.d.ts +10 -0
  115. package/dist/sync/push.d.ts.map +1 -1
  116. package/dist/sync/push.js +84 -31
  117. package/dist/sync/push.js.map +1 -1
  118. package/dist/sync/status.d.ts +10 -0
  119. package/dist/sync/status.d.ts.map +1 -1
  120. package/dist/sync/status.js +26 -0
  121. package/dist/sync/status.js.map +1 -1
  122. package/dist/sync/taxonomies.d.ts +14 -5
  123. package/dist/sync/taxonomies.d.ts.map +1 -1
  124. package/dist/sync/taxonomies.js +104 -6
  125. package/dist/sync/taxonomies.js.map +1 -1
  126. package/dist/sync/terms.d.ts +32 -0
  127. package/dist/sync/terms.d.ts.map +1 -0
  128. package/dist/sync/terms.js +62 -0
  129. package/dist/sync/terms.js.map +1 -0
  130. package/dist/utils/config.d.ts +12 -0
  131. package/dist/utils/config.d.ts.map +1 -1
  132. package/dist/utils/config.js +27 -0
  133. package/dist/utils/config.js.map +1 -1
  134. package/dist/utils/frontmatter.d.ts +40 -3
  135. package/dist/utils/frontmatter.d.ts.map +1 -1
  136. package/dist/utils/frontmatter.js +188 -5
  137. package/dist/utils/frontmatter.js.map +1 -1
  138. package/package.json +2 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,101 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `emdash-emit` (`emit`) project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [0.1.11] - 2026-11-09
11
+
12
+ ### Fixed
13
+ - **Doctor Media References False-Positive (D1)**:
14
+ - Resolved `/_emdash/api/media/file/<storageKey>` references against indexed `media/*.yaml` companion documents and local media files.
15
+ - Fixes false-positive warnings where valid remote canonical image references were falsely reported as missing from disk.
16
+ - **Push 3-Way Merge Taxonomy Preservation (D2)**:
17
+ - Ensured remote taxonomy terms are fetched via `/_emdash/api/content/{collection}/{id}/terms/{category,tag}` during 409 conflict resolution, rather than assuming empty taxonomies from content GET endpoint.
18
+ - Added `taxonomyConflicts` detection in `mergeTaxonomies`: when local and remote both modify taxonomies differently, push aborts to prevent silent data loss.
19
+ - **CLI Clean Error Output for Frontmatter Failures (D4)**:
20
+ - In `emit push`, trapped `FrontmatterParseError` and plain errors to display single-line actionable error messages with suggestions instead of raw Node runtime stack traces (conforming to AGENTS.md §5).
21
+ - **Single Article Pull Non-Destructive Heal Symmetry (D5)**:
22
+ - Extended the non-destructive frontmatter healing (`tryFastPathOrHeal`) to single-article pull (`emit pull <ULID>`), eliminating behavioral divergence between batch pull and single ID pull.
23
+ - **Guide & Package Distribution (D6)**:
24
+ - Added `emit delete`, `emit conflicts`, `emit doctor`, and `emit pull-media` to `emit guide`'s Handy Commands.
25
+ - Included `CHANGELOG.md` in `package.json`'s `files` distribution array.
26
+
27
+ ---
28
+
29
+ ## [0.1.10] - 2026-11-08
30
+
31
+ ### Summary of Major Architectural Upgrades (Phases 1a, 1b, 2, 3, 4)
32
+
33
+ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first, zero-data-loss content authoring environment. It eliminates phantom diffs, hardens deletion and conflict safety, enhances workspace diagnostics, and introduces targeted media syncing.
34
+
35
+ ### Added
36
+ - **Phase 1a & 1b: Non-Destructive Frontmatter & Inline Taxonomy Comments**
37
+ - **Non-destructive frontmatter healing**: Unquoted colons, invalid YAML indentations, and encoding quirks are healed non-destructively while strictly preserving local body edits.
38
+ - **Inline Taxonomy Comments (`# <Name>`)**: Frontmatter arrays for categories and tags now support trailing inline comments (e.g. `- tech # 技術`, `- 01M6... # Cloudflare`) so human and AI authors see names alongside IDs.
39
+ - **Bidirectional taxonomy resolution**: Resolves names to IDs and IDs to names cleanly, eliminating phantom diffs on repeated pulls and pushes.
40
+ - **Author Byline normalization**: Strict validation and default auto-selection of active authors for `emit new` / `sync_new`.
41
+
42
+ - **Phase 2: Slug Collision Guards, Safe Deletion, and Summary-First Status**
43
+ - **Duplicate Slug Guard (`DuplicateSlugError` / `DUPLICATE_SLUG_GUARD`)**: Prevents creating new articles whose slugs clash with existing local files (draft or published), indexed cache entries, or remote CMS posts.
44
+ - **Safe Deletion (`emit delete` / `sync_delete`)**:
45
+ - Remote `DELETE` API call with Cloudflare Workers/D1 confirmation.
46
+ - Automatic safety backup: writes a rescue copy to `posts/conflicts/<filename>.deleted.md` before unlinking.
47
+ - Protection against unpushed local modifications (`UnpushedModificationsError` / `UNPUSHED_MODIFICATIONS_PROTECTION`).
48
+ - Guard against unintended deletion of live published articles (`PublishedArticleDeletionForbiddenError`).
49
+ - Media file protection: referenced images in `media/` are preserved even when the article is deleted.
50
+ - Dry-run mode (`--dry-run` / `dryRun: true`) works without requiring authentication tokens.
51
+ - **Summary-First Status (`emit status` / `sync_status`)**: Capped at 30 actionable items and 5 recent articles to prevent context window overflow for AI coding assistants. Added query filters for `search` and `status`.
52
+
53
+ - **Phase 3: Conflict Management Suite, Workspace Doctor, and Uniform `_meta`**
54
+ - **Conflict Management Tooling (`emit conflicts` / `sync_conflicts`)**:
55
+ - `list`: Categorizes quarantined files as `⚠️ SYNC CONFLICT` (`.conflict.md`) or `🗑️ DELETED RESCUE` (`.deleted.md`) with companion metadata and sizes.
56
+ - `diff`: Displays unified diff between quarantined copy and canonical article.
57
+ - `resolve`: Supports `--accept-local` (restore quarantined content to canonical) and `--accept-canonical` (safely discard quarantine copy).
58
+ - **Guard G13 (`CANONICAL_HAS_UNPUSHED_EDITS` / `CanonicalHasUnpushedEditsError`)**: Overwriting a canonical article via `accept-local` is strictly blocked if the canonical file contains unpushed local modifications, requiring an explicit `--force` (`force: true`) flag.
59
+ - **Guard G14 (`QUARANTINED_PATH_REQUIRED` / `QuarantinedPathRequiredError`)**: Restricts resolve operations strictly within `<collection>/conflicts/`, preventing arbitrary file unlinks or overwrites.
60
+ - Warnings on restoring rescue files missing valid Crockford ULIDs.
61
+ - **Workspace Doctor (`emit doctor` / `sync_doctor`)**: Comprehensive diagnostic suite checking version compatibility, origin consistency, frontmatter integrity, conflicts/quarantine, baseline sync status, and referenced media disk existence.
62
+ - **Version Tracking**: Added `createdWithVersion` and `lastSyncedVersion` to `.emit/config.json`, updated automatically on sync operations.
63
+ - **Uniform MCP `_meta`**: Every MCP tool response (both success and error) carries uniform metadata containing `profile`, `workspace`, `origin`, and `resolvedBy` rationale.
64
+
65
+ - **Phase 4: Targeted Media Sync Optimization & Git Best Practices**
66
+ - **Targeted Media Pull (`emit pull-media --article <ULID>` / `sync_pull_media(article: <ULID>)`)**: Downloads only the media assets referenced by a specific article (frontmatter `featuredImage` and body Markdown/HTML images) without syncing the full multi-hundred media library.
67
+ - **Git Media Management Guidance**: Clear recommendations in `README.md` and `.gitignore` on excluding binary images while tracking companion YAML metadata (`media/*.yaml`).
68
+ - **Complete Documentation Harmonization**: Full alignment across `README.md`, `EMIT_MCP_INSTRUCTIONS`, and CLI command help.
69
+
70
+ ### Changed
71
+ - **README Authoring Desk & Division of Labor clarification**: Non-Goals are now stated as deliberate design decisions rather than missing capabilities — `postsSyncLimit` can be raised to effectively sync the full article set, the sliding window bounds what stays on disk (not what is reachable via `emit pull <id>`), and a new "Division of Labor with the Official EmDash MCP" section documents that bulk media library management and site-wide operations are intentionally delegated to the official EmDash MCP / Admin Dashboard. A matching FAQ entry ("comprehensive piece referencing my entire back-catalog") was added, and the `--all` note now honestly reflects that `emit pull-media --all` exists while articles deliberately omit it.
72
+ - Standardized CLI failure outputs across `emit new`, `emit delete`, and `emit conflicts` to print clean single-line actionable errors with suggestions and exit code 1, rather than dumping raw runtime stack traces.
73
+ - Hermetic test isolation: tests stub `fetch` and isolate temporary `HOME` configurations to prevent ambient leakage from developer profiles.
74
+
75
+ ### Known Limitations
76
+ - **Orphaned media records are not reported by `emit doctor`**: If a draft media upload succeeded but the subsequent article push failed, Emit keeps the record in `media_state` with `status = 'failed'` for compensation on the next run. A record that never gets retried remains visible only in the local cache and may linger as a `pending` entry in the remote media library; `emit doctor` currently checks referenced-media *disk existence* but does not yet report these failed/pending `media_state` entries (planned for a future release). Run `emit pull-media` to re-verify library state in the meantime.
77
+ - **Article-targeted media pull (`--article`) scans remote Portable Text content with a ULID regex fallback** when no local Markdown body is available, which may collect ULIDs appearing outside body image references; precise AST-based extraction is planned for a future release.
78
+
79
+ ---
80
+
81
+ ## [0.1.9] - 2026-11-04
82
+
83
+ ### Added
84
+ - **Sliding Window Staging Desk Model**: Bounded synchronizations defaulting to latest 100 articles (`postsSyncLimit`) per status (`all`, `draft`, `published`).
85
+ - **Offline Draft Media Upload Pipeline**: Standalone image references in `media/drafts/*` are automatically uploaded to EmDash R2 storage and rewritten to storageKey-based URLs during `emit push`.
86
+ - **Preflight Guards G1–G12**: Enforces strict invariants before writing to remote CMS (ID mismatch check, unintended publish guard, merge conflict marker detection, title autotrim, opaque block verification, etc.).
87
+ - **Distributed Mutex Lock**: Distributed 7-minute lease protocol via `POST /_emdash/api/content/{collection}/{id}/lock` to prevent multi-agent write collisions.
88
+
89
+ ### Changed
90
+ - Refactored `emit pull` to automatically sync taxonomies (`categories/` and `tags/`) by default alongside articles.
91
+
92
+ ---
93
+
94
+ ## [0.1.8] - 2026-10-31
95
+
96
+ ### Added
97
+ - Initial release of `emdash-emit`.
98
+ - Local-first synchronization engine for Cloudflare Workers + D1 powered EmDash CMS.
99
+ - Multi-site profile management (`emit profile add`, `list`, `use`, `remove`).
100
+ - Native SQLite cache database using Node.js built-in `node:sqlite`.
101
+ - Initial Model Context Protocol (MCP) server support over stdio.
package/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  - [Unpushed Edits Safeguard](#3-unpushed-edits-safeguard)
18
18
  - [Pure TypeScript & Zero Native Builds](#4-pure-typescript--zero-native-builds)
19
19
  - [Distributed Mutex Lock & 3-Way Merge](#5-distributed-mutex-lock--3-way-merge)
20
+ - [Division of Labor with the Official EmDash MCP](#6-division-of-labor-with-the-official-emdash-mcp)
20
21
  - [🌟 Key Features](#-key-features)
21
22
  - [📦 Installation](#-installation)
22
23
  - [🚀 Initial Setup & Profiles](#-initial-setup--profiles)
@@ -39,6 +40,9 @@
39
40
  - [`emit new`](#emit-new)
40
41
  - [`emit pull`](#emit-pull)
41
42
  - [`emit push`](#emit-push)
43
+ - [`emit delete`](#emit-delete)
44
+ - [`emit conflicts`](#emit-conflicts)
45
+ - [`emit doctor`](#emit-doctor)
42
46
  - [`emit status`](#emit-status)
43
47
  - [`emit diff`](#emit-diff)
44
48
  - [`emit pull-taxonomies`](#emit-pull-taxonomies)
@@ -85,9 +89,11 @@ To prevent user expectations from misaligning with the tool's purpose, it is imp
85
89
 
86
90
  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
91
 
92
+ These Non-Goals are **deliberate design decisions, not missing capabilities**: raising `postsSyncLimit` (see below) can in fact fetch tens of thousands of articles, so Emit *could* clone a whole site — it chooses not to expose that as its default workflow. The sliding window is a boundary on what stays *on disk*, never on what you can *reach*: every historical article remains editable through an on-demand `emit pull <id>`, and writing a long-form piece that cites or revises many past posts works exactly the same way — fetch each referenced article by ULID, edit it, push it. For operations that genuinely belong to the whole library (bulk media management, site-wide content queries), pair Emit with the [official EmDash MCP](#6-division-of-labor-with-the-official-emdash-mcp) instead of asking Emit to reinvent them.
93
+
88
94
  > [!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.
95
+ > **Can the sync limit be adjusted — and why is there no `--all` for articles?**
96
+ > Yes. Set `postsSyncLimit: 300` (or any larger number, e.g. your full article count) in `.emit/config.json`, and Emit's internal pagination engine automatically batch-fetches up to that configured limit. What Emit deliberately omits is a `--all` *flag* for articles: pulling the entire history into the workspace is an explicit configuration choice, not a routine default, so the "staging desk" discipline is preserved in day-to-day UX while remaining fully escapable when you truly want a larger working set. Media is intentionally different — `emit pull-media --all` does exist, because media files are inert assets that never carry unsynced writing risk.
91
97
 
92
98
  ### 2. The Sliding Window "Staging Desk" Model
93
99
 
@@ -119,12 +125,25 @@ To prevent concurrent race conditions when multiple authors or AI agents collabo
119
125
  * Emit acquires a distributed 7-minute lease via `POST /_emdash/api/content/{collection}/{id}/lock` before writing to remote articles, with automatic heartbeats and `try/finally` release guarantees.
120
126
  * If concurrent edits occur, Emit utilizes a **Hybrid 3-Way Merge** engine (`node-diff3`), merging YAML frontmatter fields and Markdown body text independently.
121
127
 
128
+ ### 6. Division of Labor with the Official EmDash MCP
129
+
130
+ Emit is intentionally not a complete EmDash client. The [official EmDash MCP server](https://github.com/aroooy/emdash) (and the Admin Dashboard) remains the canonical surface for whole-library operations, and the two tools are designed to be used **side by side against the same site**:
131
+
132
+ | Operation | Right Tool |
133
+ | --- | --- |
134
+ | Drafting, reviewing, diffing, publishing articles with safety guards | **Emit** (`emit pull` / `emit push`, MCP `sync_*` tools) |
135
+ | On-demand retrieval of any single historical article by ULID | **Emit** (`emit pull <id>`) |
136
+ | Bulk media library management (mass upload, mass delete, full-library listing) | **Official EmDash MCP** (e.g. `media_upload`) / Admin Dashboard |
137
+ | Site-wide content queries, arbitrary collection operations outside the authoring loop | **Official EmDash MCP** raw content tools |
138
+
139
+ This division is a design stance, not a limitation to work around: Emit focuses its guardrails (G1–G14, locks, 3-way merge, quarantine) on the *authoring loop* where data loss actually happens, and leaves fleet-scale operations to the server-native tooling that already handles them safely.
140
+
122
141
  ---
123
142
 
124
143
  ## 🌟 Key Features
125
144
 
126
145
  * **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.
146
+ * **Sliding Window Synchronization**: Automatically manages an active sliding window of recent posts (default: latest 100), pruning older cached entries from disk. The window bounds what stays on disk, not what you can reach — any historical article remains available via `emit pull <id>`, and the window size is configurable (`postsSyncLimit`) when a larger working set is genuinely needed.
128
147
  * **Unpushed Edits Safeguard**: Detects unpushed local changes outside the sync window and aborts sync safely before any files can be deleted.
129
148
  * **Git-Style Workspace Encapsulation**: Houses local SQLite cache (`node:sqlite`) and workspace configuration neatly inside a `.emit/` hidden folder.
130
149
  * **Multi-Site Profile Management**: Manage multiple EmDash sites and credentials in `~/.config/emit/profiles.yaml` with automatic workspace directory detection.
@@ -463,7 +482,7 @@ You can author articles completely offline using local images and let `emit push
463
482
 
464
483
  #### Workflow B: Pre-Uploaded Media & Featured Hero Images
465
484
 
466
- 1. **Upload via Admin / EmDash MCP**: Upload images via the **EmDash Admin Web Dashboard** (`/admin/media`) or via the official EmDash MCP tool (`media_upload`).
485
+ 1. **Upload via Admin / EmDash MCP**: Upload images via the **EmDash Admin Web Dashboard** (`/admin/media`) or via the official EmDash MCP tool (`media_upload`) — bulk media library management is intentionally the official tooling's domain (see [Division of Labor](#6-division-of-labor-with-the-official-emdash-mcp)).
467
486
  2. **Sync Media Locally (optional)**: Run `emit pull-media` in your workspace to download images and companion `.yaml` metadata files into `media/`. Note that `emit pull-media --prune` safely ignores `media/drafts/`.
468
487
  3. **Embed in Articles**:
469
488
  - For featured hero images: set `featuredImage: "01M67P0MBMJQQXH2K95HHCYRYB"` (26-character canonical media record `id`) or `null` in frontmatter.
@@ -505,9 +524,9 @@ Whether you are a human developer or an autonomous AI agent (Antigravity, Claude
505
524
  * To push a draft: `emit push posts/draft/<filename>.md`
506
525
  * To publish live: update `status: published` and run `emit push posts/draft/<filename>.md --allow-publish`.
507
526
 
508
- ### Preflight Safety Guardrails (G1–G12)
527
+ ### Preflight Safety Guardrails (G1–G14)
509
528
 
510
- To ensure autonomous AI agents and automated scripts cannot corrupt workspace state or accidentally publish unintended content, Emit enforces 12 strict preflight guardrails before executing any write operation:
529
+ 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:
511
530
 
512
531
  | Guard | Code Name | Description | Severity |
513
532
  | --- | --- | --- | --- |
@@ -523,6 +542,9 @@ To ensure autonomous AI agents and automated scripts cannot corrupt workspace st
523
542
  | **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 |
524
543
  | **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 |
525
544
  | **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 |
545
+ | **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 |
546
+ | **G14** | `QUARANTINED_PATH_REQUIRED` | Restricts conflict resolution targets strictly within `<collection>/conflicts/`, preventing accidental overwrites or unlinks outside the quarantine directory | Error |
547
+ | **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 |
526
548
 
527
549
  ---
528
550
 
@@ -715,6 +737,66 @@ emit push posts/draft/20261104_01M6...-my-article.md --allow-publish
715
737
 
716
738
  ---
717
739
 
740
+ ### `emit delete`
741
+
742
+ Safely deletes a local Markdown article and its remote counterpart on EmDash.
743
+
744
+ ```bash
745
+ emit delete <filePathOrId> [--allow-delete-published] [--dry-run] [-y, --yes] [-p, --profile <name>]
746
+ ```
747
+
748
+ * `<filePathOrId>`: Relative/absolute path to the Markdown file, or 26-character article ULID.
749
+ * `--allow-delete-published`: Required when deleting a published article (safety guardrail).
750
+ * `--dry-run`: Previews the deletion, inspecting referenced media and lines without modifying disk or remote.
751
+ * `-y, --yes`: Skips interactive confirmation prompt.
752
+ * `-p, --profile <name>`: Explicitly target a specific connection profile.
753
+
754
+ > [!NOTE]
755
+ > **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.
756
+
757
+ ---
758
+
759
+ ### `emit conflicts`
760
+
761
+ Inspects, diffs, and resolves quarantined sync conflict copies (`.conflict.md`) and deletion rescue backups (`.deleted.md`) residing in `posts/conflicts/`.
762
+
763
+ ```bash
764
+ # List all quarantined copies with status labels and canonical file paths
765
+ emit conflicts list [-c, --collection <collection>]
766
+
767
+ # View line-by-line diff between quarantined copy and canonical article
768
+ emit conflicts diff <filePath> [-c, --collection <collection>]
769
+
770
+ # Resolve conflict with a specific strategy
771
+ emit conflicts resolve <filePath> --accept-local [-f, --force] [-c, --collection <collection>]
772
+ emit conflicts resolve <filePath> --accept-canonical [-c, --collection <collection>]
773
+ ```
774
+
775
+ * `list`: Scans `<collection>/conflicts/`, classifying items as `⚠️ SYNC CONFLICT` or `🗑️ DELETED RESCUE`, showing sizes and dates.
776
+ * `diff`: Displays a unified diff against the current canonical article.
777
+ * `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.
778
+ * `resolve --accept-canonical`: Safely discards the quarantined copy, retaining the canonical version.
779
+
780
+ ---
781
+
782
+ ### `emit doctor`
783
+
784
+ Runs an automated diagnostic health check across the local workspace and cache database.
785
+
786
+ ```bash
787
+ emit doctor [-c, --collection <collection>] [-p, --profile <name>]
788
+ ```
789
+
790
+ Validates 6 critical health vectors:
791
+ 1. **Version Compatibility**: Checks tool version against `createdWithVersion` / `lastSyncedVersion` in config.
792
+ 2. **Origin Consistency**: Verifies `.emit/config.json`, profile origin, and environment variables.
793
+ 3. **Frontmatter Integrity**: Ensures all local Markdown files have valid parseable frontmatter.
794
+ 4. **Conflicts & Quarantine**: Flags unresolved conflict copies and deletion backups in `conflicts/`.
795
+ 5. **Sync Status**: Verifies local baseline hash tracking.
796
+ 6. **Referenced Media Existence**: Validates that all local media referenced in article Markdown bodies exist on disk.
797
+
798
+ ---
799
+
718
800
  ### `emit status`
719
801
 
720
802
  Displays the local workspace synchronization status compared against the baseline cache.
@@ -787,13 +869,13 @@ Available Tags:
787
869
 
788
870
  ### `emit pull-media`
789
871
 
790
- Downloads media assets and their companion metadata YAML files into `media/`.
872
+ 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.
791
873
 
792
874
  ```bash
793
- emit pull-media [-l, --limit <n>] [--all] [--prune] [--metadata-only]
794
-
875
+ emit pull-media [-a, --article <id-or-path>] [-l, --limit <n>] [--all] [--prune] [--metadata-only]
795
876
  ```
796
877
 
878
+ * `-a, --article <id-or-path>`: Pulls only media assets referenced by the specified article (featured image and body images), saving bandwidth and disk space.
797
879
  * `-l, --limit <n>`: Maximum number of media items to fetch (default: `300`).
798
880
  * `--all`: Fetch all media items without limit.
799
881
  * `--prune`: Remove local media files that no longer exist on remote.
@@ -877,18 +959,21 @@ Emit features a first-class **Model Context Protocol (MCP)** server over stdio.
877
959
 
878
960
  | Tool | Purpose | Key Parameters |
879
961
  | --- | --- | --- |
880
- | `sync_status` | Inspect local sync state (articles newest-first, plus `invalid` and `conflicts` diagnostics) | `collection`, `profile` |
962
+ | `sync_status` | Inspect local sync state (summary-first structure with capped actionRequired, articles, and search filters) | `collection`, `status`, `search`, `profile` |
881
963
  | `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` |
882
- | `sync_new` | Scaffold new article with canonical ULID, frontmatter, default byline, and taxonomy hints | `slug` (required), `title`, `collection`, `locale`, `byline`, `profile` |
964
+ | `sync_new` | Scaffold new article with canonical ULID, frontmatter, default byline, duplicate slug guard, and taxonomy hints | `slug` (required), `title`, `collection`, `locale`, `byline`, `profile` |
883
965
  | `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` |
966
+ | `sync_delete` | Safely delete article locally and remotely with rescue backup (`.deleted.md`) and unpushed edit protection | `filePath`, `id`, `allowDeletePublished`, `dryRun`, `profile` |
967
+ | `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` |
968
+ | `sync_doctor` | Run comprehensive workspace diagnostics (version, origin, frontmatter, conflicts, sync, referenced media) | `collection`, `profile` |
884
969
  | `sync_diff` | Inspect diff between working file and baseline snapshot | `filePath`, `id`, `profile` |
885
970
  | `sync_pull_taxonomies` | Pull remote category and tag definitions (standalone; returns taxonomy hints) | `profile` |
886
- | `sync_pull_media` | Pull remote media files and metadata into `media/` | `limit`, `all`, `downloadFiles`, `profile` |
971
+ | `sync_pull_media` | Pull remote media files and metadata into `media/` (supports batch or targeted article media) | `article`, `limit`, `all`, `downloadFiles`, `profile` |
887
972
  | `profile_list` | List all configured connection profiles | (none) |
888
973
  | `profile_switch` | Switch active profile for multi-site environments | `name` (required), `persist` |
889
974
 
890
975
  > [!NOTE]
891
- > **Every tool failure is a structured result, never a raw throw.** Handlers return `isError: true` with the same diagnostics the CLI prints — `[CODE]`, HTTP status, endpoint, and a `💡 Suggestion` line (or a `PREFLIGHT_VALIDATION_FAILED` JSON payload with guard codes). This includes workspace/profile resolution failures, which list the available profiles so the agent can retry with an explicit `profile` argument.
976
+ > **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.
892
977
 
893
978
  ### Autonomous Agent Workflows
894
979
 
@@ -941,6 +1026,10 @@ Push your changes with 'emit push <file>' or discard them before pulling.
941
1026
  * If you want to discard local edits: Delete or revert the file, then re-run `emit pull`.
942
1027
  * If you want to temporarily skip pruning: Run `emit pull --no-prune`.
943
1028
 
1029
+ ### Q: I want to write a comprehensive piece that references my entire back-catalog — is that supported?
1030
+
1031
+ **Yes.** The sliding window limits which articles *stay on disk*, not which articles you can *work with*. To reference, quote, or revise any historical post, fetch it individually: `emit pull <article-ULID>` (MCP: `sync_pull` with `id`). Each fetched article enters the normal authoring loop — edit, diff, push — with all guardrails applied. If you prefer a permanently larger local working set instead, raise `postsSyncLimit` in `.emit/config.json`; Emit's pagination engine will fetch up to that size automatically. What Emit deliberately does not offer is a default "clone the whole site" mode; for genuine whole-library operations (bulk media management, site-wide queries), use the official EmDash MCP alongside Emit — see [Division of Labor with the Official EmDash MCP](#6-division-of-labor-with-the-official-emdash-mcp).
1032
+
944
1033
  ### Q: `PushValidationError`: Cannot publish article live without `--allow-publish`
945
1034
 
946
1035
  **Cause**: Frontmatter has `status: published`, but `emit push` was executed without `--allow-publish`.
@@ -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"}