npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@heartidentity/datoeditsync

v0.5.2

Published

Mirror a DatoCMS project as local text files so LLMs can edit content with plain file tools — with diff review before writing back.

Readme

@heartidentity/datoeditsync

Mirror a DatoCMS project as local text files so LLMs (Claude Code / Cowork) can edit content with normal file tools — with a diff review before anything is written back, three-way conflict detection like git, and drafts-only writes.

Editing DatoCMS content through the API/MCP is slow and unreviewed: every read and write is a round-trip, changes go live immediately, and there's no full-text search over the content. datoeditsync pulls the content down as JSON files. The LLM edits files (Read / Edit / Grep), the tool shows a diff before pushing, checks for conflicts, and writes back only after confirmation — always as drafts.

Quick start

npx @heartidentity/datoeditsync init

This prompts for a DatoCMS Content Management API token, validates it, pulls the schema and content, generates a Claude skill + CLAUDE.md tailored to the project, and offers to git init (recommended — content edits are hard to undo without it). Then just edit files under content/ and sync back.

Commands

| Command | What it does | |---|---| | init | Interactive setup: token, schema + content pull, skill/CLAUDE.md, .gitignore. Flags: --token, --environment, --dir, --git, --hook. | | pull [record] | Remote → local. Refreshes content/, schema/ and the base snapshot; merges concurrent remote edits into the working copy. --model <api_key>, --force (scoped to [record] when given). | | status | Local changes vs base: new / modified / deleted, per record with affected fields. | | diff [target] | Detailed field diff (coloured in a TTY, plain with --no-color). | | validate | Check local files against the schema: required fields, enums, allowed block types, item limits, tree placement. Runs automatically before every push. | | push | Dry-run by default — shows exactly what would change and checks conflicts. --apply executes. Writes drafts only. --record <id>, --model, --delete <id>. | | publish | Separate, explicit step. Dry-run by default; --record <id> / --all with --apply publishes. | | new <api_key> | Create a local record template (id: "$new") from the schema. For a block model the template is printed instead of written. | | regen | Rewrite the generated files (skill, CLAUDE.md, schema overview) from local state — offline and idempotent. --check (CI), --diff, --force. | | changelog | What changed in datoeditsync since this project was last generated. --since <version>, --all. |

Every command accepts --json for machine-readable output. Exit codes: 0 ok · 1 error · 2 conflict · 3 validation failure · 4 not a project · 5 generated files out of date.

Invocation

Nothing is installed: the generated SKILL.md and CLAUDE.md spell every command as npx @heartidentity/datoeditsync@latest …. A documented command therefore works on any machine that has npx, and the tool that runs is always the one the instructions were written for — there is no installed copy that can drift.

init also offers a SessionStart hook (--hook / --no-hook) which runs pull whenever a Claude Code session starts in the project, so an editor never has to remember to sync. It is opt-in: the harness then runs that command unattended, and it needs network access.

Staying current

init sets a project up; pull and regen keep it up to date. The generated files carry a provenance stamp (<!-- generated by datoeditsync X.Y.Z -->) and .datoeditsync/config.json records the hash of each one as it was written.

  • pull regenerates them whenever the tool or the schema has moved on — but only files that still match what it wrote. Anything edited by hand is left alone and reported.
  • status and push warn when they are out of date, so drift surfaces where you already are.
  • regen --check exits 5 on drift, for CI.

This matters more than ordinary documentation rot: SKILL.md is the contract an agent acts on. When push changed from "conflict → skip" to "merge → MERGE", an agent on the old skill would have kept reporting conflicts that no longer exist — confidently.

How it works

Three-way model like git: base (snapshot from the last sync), local (your working copy), remote (DatoCMS).

  • Push compares local vs base to find changes, then merges them against the current remote field by field — and for localized fields locale by locale. Only the fields you actually changed are written (with the record's current_version as an optimistic lock), so a colleague editing other fields of the same record at the same time loses nothing; such a record is reported as MERGE rather than UPDATE.
  • A conflict is the same field — same locale — changed on both sides. Those records are skipped; push --json reports base, local and remote for each colliding field, so a human or an LLM can resolve it semantically. Take the remote version for one record with pull --force <id>.
  • Not merged automatically: two edits inside one string, one locale of a block-bearing field (rich/structured text), or one plain object field (SEO, file references). Those are real conflicts and are shown.
  • Pull uses the same merge: it folds remote changes into records you have edited locally instead of refusing, and reports only genuine collisions.
  • New records use id: "$new" → created remotely, with the real id written back and the file renamed.
  • Deletions are explicit: set "_delete": true in the file or use push --delete <id>. Deleting a file on disk does nothing.
  • Blocks are stored inline as { "id", "type", "fields" }. Keep the id to update, use "$new" to add, remove the object to delete.
  • Tree models keep their placement in the file: parent_id (the parent record's id, or null at the root) and position sit next to id, outside fields. Editing them moves the record on the next push; they are merged and conflict-checked like any other value. Deletion ignores them, since DatoCMS renumbers position whenever a sibling moves.
  • Assets: existing assets are referenced by upload id. New files go into media/ and are referenced as { "$path": "media/<file>", "alt": "…" }; push --apply uploads them (deduplicated by content hash, tracked in .datoeditsync/uploads.json) and rewrites the reference to the real upload_id.

File layout

my-project/
├── .datoeditsync/
│   ├── config.json        # project id, environment, options, generated-file
│   │                      # provenance (never the token)
│   ├── base/<model>/<id>.json   # three-way base snapshot
│   ├── meta.json          # updated_at + hash per record at last sync
│   └── uploads.json       # md5 → upload id (media dedup manifest)
├── content/<model>/<id>__<slug>.json
├── media/                 # new assets, referenced via { "$path": "media/…" }
├── schema/<model>.json + _overview.md
├── .claude/skills/dato-edit/SKILL.md
├── CLAUDE.md
└── .env                   # DATOCMS_API_TOKEN (gitignored)

Auth

v1 uses a CMA API token (full-access or a custom role), stored in .env as DATOCMS_API_TOKEN and read at runtime. It is never written to config.json and never logged.

Development

npm install
npm run build        # compile to dist/
npm test             # unit tests for the diff / merge / conflict engine
npm run dev -- status  # run the CLI from source
npm run test:integration  # live end-to-end test against a real DatoCMS project

The integration test needs DATOCMS_TEST_TOKEN (a CMA token for a dedicated test project, with schema + environment rights) — set it in the environment or put DATOCMS_TEST_TOKEN=… into a .env in the repo root (gitignored). Without the token it skips cleanly.

Non-goals (v1)

No asset editing/deletion (uploading new files via $path is supported; managing the media library is not), no schema migrations (schema is read-only), no realtime/watch mode, no multi-user locking — the three-way merge plus DatoCMS' current_version optimistic lock is the answer to concurrent editing.

License

MIT