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

overleaf-review

v0.7.0

Published

The missing review layer for Overleaf's Git bridge — sync comments and tracked changes between Overleaf and your local repo.

Readme

overleaf-review

The missing review layer for Overleaf's Git bridge.

Sync comments and tracked changes between Overleaf and your local repo — both directions.

npm version npm downloads license node

Overleaf ⇄ overleaf-review ⇄ your git repo + editor + AI tools


The problem

Overleaf's Git integration syncs your .tex source — but silently drops the entire collaborative review layer. Co-authors' comments (with their text anchors) and tracked changes live in Overleaf's own database and never reach your repo. So if you draft locally (say, in VS Code with an AI assistant) and sync via Git, you can't see the feedback your co-authors leave in Overleaf, and you can't push suggestions back.

overleaf-review bridges that gap — a direct Overleaf client that runs alongside your Git workflow and carries the review layer Git can't represent.

✨ Features

  • 📥 pull — read comments + tracked changes (with anchors) into a Git-friendly sidecar (.overleaf/reviews.md + .json), so your tools have every co-author note in context.
  • 📤 push — turn local edits into tracked-change suggestions, mapping files to Overleaf docs by path (one file, or every changed .tex at once). A saved Base/Local/Live merge keeps non-overlapping co-author edits, while overlapping edits and active tracked-change ranges stop the push for inspection. --direct sends plain edits instead of suggestions.
  • 🔄 fetch — write Overleaf's current text back down into your repo (read-only on Overleaf, so it cannot disturb a single comment or tracked change) and save the synchronization base used by future pushes. Existing local files are snapshotted before replacement.
  • 📋 review plan / review submit — save a complete, binding push plan, inspect it, then submit it only if the local file, saved base, live document version, and review ranges are still unchanged.
  • 🖼️ upload — push figures, PDFs, or new files into Overleaf.
  • 💬 comment / reply / resolve / delete-comment / delete-message — full comment control: start a thread, reply, resolve/reopen, delete a whole thread or a single message. Identical recent comments and replies are retry-safe by default; --force deliberately posts another copy.
  • ✅ accept / reject — act on collaborators' tracked changes and verify removal of every requested change id before reporting success; rejection also verifies the exact resulting text.
  • 🧾 Review receipts — pushes, accept/reject actions, comments, and replies leave durable JSON records under .overleaf/receipts/ showing what was verified or left ambiguous.
  • 🔑 login — validated auth stored outside your repo (chmod 600); --browser mode is institutional-SSO friendly.

⚠️ Don't mix this with Overleaf's Git/GitHub sync

Overleaf's Git integration writes documents by wholesale content replacement. The review layer is stored separately, anchored by character offsets — so a bulk overwrite orphans or displaces your comments and tracked changes. (Overleaf's own docs advise against combining Git with track changes.) This isn't something a tool can patch around; it's inherent to how the bridge writes.

overleaf-review writes through Overleaf's real-time OT API instead — incremental insert/delete ops that Overleaf transforms the review ranges against, so comments and tracked changes survive.

Recommended setup: unlink Overleaf's Git/GitHub sync and let overleaf-review be the only bridge.

| Need | Command | | --- | --- | | Begin a local review session | review start | | Overleaf text → your repo and save Base | fetch | | Your edits → Overleaf, as suggestions | push | | Your edits → Overleaf, directly | push --direct | | Inspect, then submit the exact same change | review plan --out plan.json, then review submit --plan plan.json | | Figures / new files → Overleaf | upload |

Your Git repo stays a completely normal repo — commit whatever you like, .tex included — and nothing bidirectional exists that can clobber the review record. (Renaming/deleting files is still done in the Overleaf UI.)

📦 Install

npm install -g overleaf-review

🚀 Quick start

# 1. Authenticate (stored in ~/.config/overleaf-review/, never in the repo)
overleaf-review login             # paste your overleaf_session2 cookie, or:
overleaf-review login --browser   # opens your Chrome, log in normally (SSO works)

# 2. Link this repo to an Overleaf project (id from the project URL)
overleaf-review link --project 6a4c…bec5a

# 3. Start from Overleaf's current text and review state
overleaf-review review start                # fetch text + save Base + pull reviews

# 4. Edit locally, then inspect and submit a binding plan
overleaf-review review plan --out .overleaf/push-plan.json
overleaf-review review submit --plan .overleaf/push-plan.json

# Or use the safe one-shot form when a separate approval step is unnecessary
overleaf-review push --dry-run              # transient preview; sends nothing
overleaf-review push                        # plan, validate, send, and verify

# 5. Work with review threads
overleaf-review comment --anchor "Introduction" --message "Expand this section"
overleaf-review resolve --thread <id>       # thread ids come from `pull`

fetch and review start write Overleaf's text into the local files, so commit or otherwise preserve local-only work before running them. They save .overleaf/base.json; edit only after that base is established. Existing installations should run one of these commands once before their first safe push. If an upgraded repository already has unpushed edits, copy or stash them, run fetch, then restore the edits so the new Base remains the Overleaf version they were based on.

🧭 Commands

| Command | What it does | | --- | --- | | login [--cookie <v>] [--browser] | Authenticate and store your session (SSO-friendly --browser) | | link --project <id> | Link this repo to an Overleaf project (.overleaf/config.json) | | pull [--out <dir>] | Read comments + tracked changes into a sidecar | | fetch [--file <f>] [--dry-run] | Write Overleaf text locally, snapshot replaced files, and save Base (read-only on Overleaf) | | review start [--file <f>] [--out <dir>] | Fetch text, save the synchronization base, and pull the review sidecar | | review plan --out <plan.json> [--file <f>] [--edits <blocks.json>] [--doc <name>] [--direct] [--allow-overlap] [--unsafe-no-base] | Create a complete binding plan without changing Overleaf | | review submit --plan <plan.json> [--acknowledge-ambiguous] | Submit that plan only if all recorded preconditions still match | | review consolidate --doc <path> --author <user-id> --out <preview.json> [--change <id> …] | Preview consolidation of existing suggestions and save a full backup; no Overleaf changes | | review consolidate --apply --plan <plan.json> | Apply and verify a checked consolidation plan for your own suggestions | | upload <path…> [--folder <name>] | Upload figures / new files into Overleaf | | push [--file <f>] [--doc <name>] [--direct] [--dry-run] [--plan-out <path>] [--plan <path>] [--allow-overlap] [--unsafe-no-base] [--acknowledge-ambiguous] | Safely merge and send local edits (all changed .tex if no --file) | | comment --anchor <text> --message <text> [--doc <name>] [--nth <n>] [--force] | Add an anchored comment; recent identical retries are skipped unless forced | | reply --thread <id> --message <text> [--force] | Reply to a thread; recent identical retries are skipped unless forced | | edit-message --thread <id> --message-id <id> --before <text> --message <text> --out <plan.json> | Plan an in-place edit of your own existing message; sends nothing | | edit-message --manifest <edits.json> --out <plan.json> | Preview a batch of exact before/after message edits | | edit-message --plan <plan.json> | Apply the saved message-edit plan with readback and per-message receipts | | resolve --thread <id> [--reopen] | Resolve (or reopen) a comment thread | | delete-comment --thread <id> | Delete a whole comment thread | | delete-message --message-id <id> [--thread <id>] | Delete a single message within a thread | | accept --change <id> … | Accept collaborators' tracked change(s) | | reject --change <id> … | Reject collaborators' tracked change(s) |

Thread and change ids are listed by pull (in .overleaf/reviews.md).

Editing existing replies in place (v0.7.0)

Use edit-message for correcting your own existing comments or replies without deleting and reposting them. This uses Overleaf's native edit endpoint: message IDs, authorship and original posting times stay intact; Overleaf may add/update edited_at. It sends no manuscript operations, does not move comment anchors, and does not resolve/reopen threads. Other authors' messages cannot be edited. A material change to an answer someone has already responded to may be clearer as a new follow-up message instead.

For one message, supply its exact current text and proposed replacement:

overleaf-review edit-message --thread <thread-id> --message-id <message-id> \
  --before 'Original reply.' --message 'Corrected reply.' --out message-plan.json
overleaf-review edit-message --plan message-plan.json

For longer replies or batches, prepare message-edits.json using exact IDs from reviews.json:

{
  "edits": [
    {
      "threadId": "0123456789abcdef01234567",
      "messageId": "0123456789abcdef01234568",
      "before": "Original reply.",
      "after": "Corrected reply.\nA second paragraph, if needed."
    }
  ]
}
overleaf-review edit-message --manifest message-edits.json --out message-plan.json
# Inspect all before/after edits before applying:
overleaf-review edit-message --plan message-plan.json

Planning is read-only on Overleaf. It binds the server, project, authenticated author, exact message IDs/text and full targeted threads. All targets are checked before the first write and again before each subsequent write. Changes to a targeted thread—including new replies, edits, or resolution changes—stop the batch; activity in unrelated threads does not block it. Apply accepts only the saved plan, without content overrides or a force flag.

Every edit is verified by reading back the same message ID and replacement content, with unchanged authorship, posting time, other messages and thread metadata. A receipt in .overleaf/receipts/ retains the plan's complete original threads, before/after text, and per-message progress.

Batches are sequential, not atomic. If a later request is definitively rejected, resubmitting the same plan rechecks all targets and resumes without repeating verified edits. A lost response is recovered only when exact readback proves the intended result. If the outcome stays uncertain, there is no blind resend, rollback, or delete-and-repost fallback; even a different plan targeting that thread is blocked until the original attempt is reconciled. Keep the original plan/receipts.

Use a quiet editing window: the native endpoint has no expected-content/version condition, so the preflight cannot prevent an edit racing between the last read and the POST. Verification detects observable discrepancies but cannot undo an already-sent overwrite or prove that no transient concurrent edit was overwritten. No server-side compare-and-swap or atomic batch is claimed.

npm run probe:message-edit-live -- --project <test-id> --confirm-test-project is an opt-in live check using the built CLI. It creates a new scratch document, posts and edits two test replies, verifies retry behavior, and checks that existing documents and threads were unchanged. Never run this probe against a manuscript project.

Safe pushes

A normal push compares three versions of each document:

  • Base — the Overleaf text saved by the last fetch or successful push;
  • Local — your current file;
  • Live — the document as it exists in Overleaf when planning.

Your Base→Local intent is rebased onto Live. Non-overlapping live edits are preserved; genuinely overlapping Base→Local and Base→Live edits abort instead of silently undoing a collaborator's work. A proposed edit that intersects an active tracked-change range is also blocked by default and the relevant change ids are listed.

For intentional sentence or paragraph rewrites, agents should preserve their chosen edit boundaries with --edits, rather than relying on automatic diff grouping. Edit the local file normally, then describe every change in a JSON manifest:

{
  "replacements": [
    {
      "before": "This is the old paragraph. Its explanation is unclear.",
      "after": "This is the revised paragraph. Its explanation is clearer."
    }
  ]
}
overleaf-review review plan --file main.tex --edits blocks.json --out plan.json
overleaf-review review submit --plan plan.json

Each block becomes one deletion and/or insertion, even across multiple sentences or LaTeX markup. Overleaf controls the resulting review UI; a replacement is generally two tracked ranges, not a guaranteed single accept/reject button. Keep typo corrections small, group a coherent rewrite, and keep independent decisions separate. Author attribution remains unchanged.

before must match the saved Base exactly. Repeated text requires a 1-based occurrence. Blocks must be separated and must explain the entire local diff. The plan embeds the resolved blocks; it never rereads or silently reinterprets the manifest during submission. Concurrent edits outside blocks are preserved; any comment, pending suggestion, or concurrent edit touching a block stops ordinary block planning rather than silently fragmenting it. For deliberately reviewed overlaps, use the manifest fields below; otherwise narrow the block or resolve the conflict. --edits requires --file and a saved base; it cannot be combined with --direct or --unsafe-no-base.

Without explicit blocks, tracked suggestions group nearby word edits into phrase replacements. For example, rewriting “old model predicts low” as “revised model explains high” produces one deletion and one insertion, instead of six separate word operations. Isolated corrections stay small. Grouping bridges at most three unchanged words (40 characters), limits combined spans to 320 characters, and stops at detected sentence/clause boundaries, paragraph breaks, and LaTeX markup. This is a deterministic readability heuristic; it does not infer which scientific claims should be accepted together. An existing multiword insertion remains a single insertion.

Unchanged comment anchors, tracked ranges, and edits made on Live since Base prevent grouping across them. Overlap checks use the full grouped footprint, and submission reproduces the same grouping. --direct retains the narrower word-level operations. Plans from before this grouping change must be regenerated; submitting an old plan does not silently regroup its approved operations.

Planning and submission check a conservative range budget: existing tracked ranges plus proposed insert/delete operations must not exceed 2,000 per document. The preview shows both counts. Overlap transformations can alter the actual count, so this is a preflight guard rather than an exact prediction. Splitting a revision into batches does not remove accumulated pending ranges. Grouping applies to new pushes; it does not consolidate or accept suggestions already on Overleaf.

Holistic paragraph rewrites over reviewed suggestions and comments

Use this when a paragraph rewrite intentionally incorporates or supersedes existing suggestions.

To regroup existing fragmented suggestions without changing their proposed wording, set before and after to the same paragraph text and explicitly list the suggestion IDs in supersedes. Keep the local file unchanged for that block. This is supported from v0.6.1; the proposal must still differ from the underlying text obtained by rejecting those suggestions. Affected comments still require explicit anchor mappings, and all stale-plan and verification checks still apply. Ordinary replacements without selected suggestions cannot be no-ops. It differs from consolidation: the proposed text may change, and selected older suggestion cards are replaced by a new pending revision under the authenticated account.

Fetch the current text/review state, edit the local file, and extend the normal block manifest:

{
  "replacements": [
    {
      "before": "The current paragraph, including pending suggestions.",
      "after": "The coherently rewritten paragraph. Its explanation incorporates the correction.",
      "supersedes": ["existing-change-id-1", "existing-change-id-2"],
      "reason": "Incorporates the correction into a more coherent explanation.",
      "comments": [
        {
          "thread": "existing-thread-id",
          "anchorAfter": "Its explanation incorporates the correction.",
          "reply": "Rewritten to incorporate your correction and clarify the explanation."
        }
      ]
    }
  ]
}
overleaf-review review plan --file main.tex --edits blocks.json --out reviewed-plan.json
overleaf-review review submit --plan reviewed-plan.json

The supersedes, comments, or reason fields select reviewed-overlap planning automatically. Every block needs a paragraph-level reason. Omit supersedes/comments or use empty arrays when that block has no corresponding overlaps. reply is optional. Use occurrence in a comment mapping when anchorAfter occurs more than once in the revised block. To highlight the entire revised paragraph, set anchorAfter equal to after.

The tool lists unapproved overlapping IDs and threads. All fragments of each selected suggestion and the full affected comment anchor must be contained in its approved block; it will not silently expand the scope. Suggestions from other authors may be explicitly superseded. Their original content and attribution are retained in the full plan/receipt, not falsely attributed to the new revision. Existing thread IDs, messages, author metadata, and resolution state are preserved; only approved anchors move. Suggestions and comments outside the blocks retain their identities and content, with positions shifted as needed.

The plan shows underlying, current, and proposed text for each block. The underlying text excludes the selected pending suggestions. Submission undoes those ranges and writes the new whole-block revision, then reanchors the existing threads in the same OT update. It does not accept older suggestions first. Rejecting the new revision restores that underlying text; it does not recreate the old suggestion cards. Later manual review uses Overleaf's native anchor behavior; this command is not a background service that restores anchors after future rejections.

Reviewed rewrites require a saved plan and synchronization base, use a separate plan kind, and cannot be forced with --allow-overlap, --direct, --unsafe-no-base or --acknowledge-ambiguous. Any unapproved overlap, changed local intent, or changed document/review state before submission blocks the write. Coordinate a quiet editing window: the server does not provide a conditional transaction spanning text, ranges and threads, and post-write verification is not a rollback.

Replies are posted only after the manuscript, anchors, range identities, author attribution and rejection baseline have been verified. A receipt separately tracks text verification and each reply. If a definite reply failure leaves follow-up pending, resubmitting the same plan resumes without rewriting the manuscript. A lost reply acknowledgement can be reconciled from its new message ID or an unambiguous new same-author message; without proof it remains quarantined and is not automatically reposted. An uncertain text update always requires manual reconciliation. Keep the plan and receipts when resuming; do not use a fresh plan to bypass an uncertain attempt.

Verification: npm run probe:reviewed-model covers other-author suggestions and existing-thread reanchoring against pinned upstream range logic. The opt-in npm run probe:reviewed-live -- --project <test-id> --confirm-test-project creates a new scratch document and threads, verifies anchor movement, a reviewed rewrite, explanatory reply and retry, and checks existing documents were unchanged. It does not impersonate another account.

Consolidation of existing suggestions

review consolidate --doc main.tex --author <user-id> --out .overleaf/consolidation.json captures the live text, all document ranges, and project comment threads. It reconstructs the text with that author's selected suggestions rejected, then plans a grouped revision back to the current proposed text. By default it considers all current suggestions by that author, including older fragments; repeat --change to select a subset. Use the author user ID from range metadata.

The artifact records both text hashes, a reverse/forward reconstruction proof, projected range counts, and any comment or unselected-suggestion blockers. It preserves the current pending proposal, not a historical review state before older suggestions were absorbed. Consolidation would assign new IDs and timestamps. Counts remain projections until server transformations are verified. Touching comment anchors or unselected changes blocks the candidate; these are not automatically removed or accepted.

For offline analysis, use --snapshot <file.json> instead of --doc. The snapshot must contain projectId, docId, docPath, version, text, ranges: { changes, comments }, and threads; the preview embeds this complete object as backup. A normal review sidecar alone is insufficient.

Planning sends nothing. Inspect the complete plan, then apply it separately:

overleaf-review review consolidate --apply --plan .overleaf/consolidation.json

Only suggestions owned by the authenticated account can be reapplied. Blocked/no-reduction plans cannot be submitted. The command reproduces the plan from its backup and requires unchanged text, document version, ranges, and project threads. It journals the full snapshot before sending undo and reapply operations together in one update. It never accepts suggestions as a prerequisite.

Verification checks the proposed text, text with selected suggestions rejected, text with all suggestions rejected, disappearance of old IDs, new-ID attribution, reduced range count, and unchanged unselected ranges, comment anchors and thread messages. New IDs and timestamps are expected. Any uncertain send or failed verification quarantines subsequent attempts: inspect the receipt and live document before manually reconciling it; there is no automatic rollback/retry. Reusing an already successful plan reports its receipt without sending it again.

Coordinate a quiet editing window for consolidation. Overleaf has no conditional transaction covering text, ranges and threads, so preflight cannot prevent every last-moment collaborator race; readback detects discrepancies but cannot make an already-sent operation un-happen. A backup is recovery evidence, not a promise of automatic review-history restoration.

Development check npm run probe:consolidation-model exercises pinned upstream range logic in memory without accessing an Overleaf project. The opt-in npm run probe:review-live -- --project <id> --confirm-test-project creates and retains a new scratch document in the specified test project, verifies consolidation and protected review state, and checks that existing documents were unchanged. It does not require or spoof another account. Foreign-author preservation is also covered by the upstream model check. Never run this probe against a manuscript project.

From v0.6.2, rejection and underlying-text reconstruction account for insertions and deletions at the same position, including multiple co-located deletions. The undo first restores selected deletions, then removes insertions at their adjusted positions; exact-text and range verification remain mandatory. Recreate unsubmitted plans from a fresh snapshot after upgrading, since the operation ordering has changed. Do not re-plan to bypass an uncertain earlier submission.

npm run probe:rejection-model checks this ordering against pinned upstream range logic in both tracking modes, including consolidation and reviewed paragraph rewrites. Add -- --backup <file> to check a saved full snapshot (or a plan containing backup) offline. The backup stays local; the probe fetches only public upstream code and never connects to an Overleaf project.

Push overrides and submission

Use --allow-overlap only after inspecting those tracked changes. --unsafe-no-base is a deliberate legacy escape hatch that treats the current Live document as Base; it loses the protection against co-author edits made since your local file was obtained. --direct changes how the validated ops are represented in Overleaf, not the merge and conflict checks.

For a separate approval step, review plan --out plan.json stores the full operation list and its preconditions, active tracked ranges, and any comment overlaps. review submit --plan plan.json re-derives the plan from Base/Local/Live and aborts if preflight finds that the project, local source, synchronization base, live document version/content, or active review ranges changed. Plans contain manuscript fragments, so store or commit them with the same care as the source itself. push --plan-out <path> and push --plan <path> expose the same workflow through the one-level command.

Overleaf can transform an update that races with a collaborator after the final preflight. The tool therefore reads every result back and fails rather than claiming success when the exact planned text is not present. A multi-file submission is not transactional; if a later file fails, its receipt identifies every earlier file already verified on Overleaf.

Pushes, accept/reject actions, comments, and replies write audit receipts beneath .overleaf/receipts/. Keep an ambiguous receipt: it means the request may have reached Overleaf but readback could not prove the result, so inspect the live project before retrying. For comments and replies, an identical recent message is treated as a retry and skipped; pass --force only when a second identical message is intentional.

An ambiguous push quarantines its affected documents even if a newly generated plan has a different timestamp or tracked-change seed. After waiting for delayed updates and manually reconciling the receipt against live Overleaf, --acknowledge-ambiguous explicitly clears that submission-time guard; it should never be used as an automatic retry flag.

Mutating commands are serialized by a working-tree-specific lock in the system temporary directory, preventing two local agents from racing through the same preflight. A normal exit removes the lock. If a process crashes and leaves it behind, the next command prints its exact path; inspect any relevant receipt and the live Overleaf document before removing it manually.

🧠 How it works

overleaf.com's editor speaks an old socket.io 0.9 protocol over a WebSocket. The client (src/overleaf-socket.ts) joins the project and reads each doc's ranges (comments + tracked changes). To write, it sends applyOtUpdate ops:

  • a tracked change is an insert/delete op with a meta.tc flag;
  • a comment is a c op plus a REST post of the message text.

push performs the Base/Local/Live merge described above, translates Live→merged text into sequential OT ops, and waits for Overleaf's applied-update event. It then reads the document back and verifies the final text—and, in suggestion mode, tracked-change ids—before updating the saved base. Accept and reject operations verify that the requested ids disappeared; rejection also verifies the exact planned text. Push, accept/reject, comment, and reply fail loudly when their readback cannot prove success.

⚠️ Disclaimer

Unofficial. Overleaf has no public API for comments or tracked changes, so this tool talks to the same internal real-time and thread endpoints the web editor uses. It is not affiliated with or endorsed by Overleaf, may break when Overleaf changes internals, and should be used on your own account and projects. Use at your own risk.

🗺️ Roadmap

  • File rename / delete (currently done in the Overleaf UI)
  • Trusted-publishing CI

📝 Changelog

Version history and notes are on the Releases page.

📄 License

MIT © Miguel Castellano