All notable changes to this project will be documented in this file.
olcli diff --latexdiffmarks the revision up inside the document (#55) - the follow-up left open when the corediffcommand shipped in 0.10.0. A unified diff is the right artifact for a developer and the wrong one for a thesis advisor, who expects deletions struck through and additions underlined. It runs on the two sidesdiffhas already fetched, so the markup describes exactly what the patch output does - struck through is what a push would overwrite, underlined is what it would upload - and costs no extra request- Requires
latexdiffon PATH, which ships with TeX Live and MacTeX. A missing binary is reported as a setup problem with the install command for the platform, not as a failure ofdiff; nothing else in the command needs an external tool \inputand\includeare inlined before comparing, with--no-flattento opt out. Without it a remote compile resolves those against the files in the project - the old content - and produces a PDF marking up the root document while showing every input file as unchanged. Wrong in a way that is very hard to notice- Output goes to
.olcli-diff/, dotted becausescanLocalFilesskips dotted entries before any ignore rule is consulted. A plainmain-diff.texnext to the document would be uploaded to Overleaf by the nextpush - The root document is the
.texfile declaring\documentclass. Several candidates are reported and listed rather than resolved by preferringmain.tex: marking up the wrong document produces a plausible PDF describing the wrong revision --latexdiff-optpasses options straight through (--latexdiff-opt --math-markup=0), since the remote tree is on disk only for the duration of the run and cannot be handed tolatexdiffby hand afterwards
- Requires
--pdfcompiles the marked-up document on Overleaf, so a reviewable PDF needs no local TeX installation - the reason the flag was proposed in #45- Overleaf's compile endpoint takes a path that must already be in the project; there is no way to compile a document that is not. So the markup is uploaded as
olcli-latexdiff.texnext to the root document, compiled, downloaded, and removed. That is a real mutation of the project for the duration of one compile, and the command says so before it does it - It refuses rather than overwrites if a file of that name already exists, deletes from a
finallywith nothing in between able to terminate the process first, and prints the exactolcli rmcommand if the delete itself fails or the run is interrupted - Placed next to the root document rather than at the project root, so relative
\includegraphicsand\bibliographypaths resolve exactly as they do for the document it was built from - A compile failure writes the CLSI log next to the marked-up source instead of reporting only a status, and deletes a PDF left by an earlier run rather than leaving one that describes a different revision. The compile runs against the project, so a
.styor.clsthat exists only locally is the usual cause and the message says so. A missing figure is not: Overleaf draws a placeholder box naming the file and still reports success
- Overleaf's compile endpoint takes a path that must already be in the project; there is no way to compile a document that is not. So the markup is uploaded as
src/latexdiff.ts- root document detection, argument construction, output naming and failure interpretation are functions over data, unit-tested with no Overleaf account and nolatexdiffbinary. 21 tests, in the suite CI already runsolcli diff --exit-codemakes the command a CI gate (#48) - the second follow-up left open when the corediffcommand shipped in 0.10.0. It reportsdiff(1)'s statuses:0when nothing differs,1when something does,2when the run itself failed- The
1vs2split is the whole feature. A pipeline that cannot separate "the project differs" from "the run failed" reads an expired session cookie as a content change, and a job that goes red for the wrong reason sends whoever reads the log looking for a diff that was never computed. All ten failure paths in the command move to2together - bad flag combinations, a missing directory, an unresolvable project, alatexdiffthat is not installed, a failed remote compile - so no failure can be mistaken for a difference - Failures stay
1when the flag is absent, which is what every other command exits, so scripts that checkolcli difffor success are unaffected - The status is set rather than exited on.
process.exitdiscards whatever is still buffered on a non-TTY stdout, andolcli diff --exit-code > patch.txtis exactly a large patch going into a pipe: measured here, exiting outright truncated a 1.5 MB patch to the 128 KB pipe buffer and lost 91% of it mid-hunk. Returning lets node flush first - The gate covers whatever was compared, so
--filenarrows it to one file the way agit diff --exit-codepathspec does, and a--filematching nothing is0rather than an error. Under--latexdiffit reports on the project rather than on the markup - a changed figure is a real difference even though a marked-up root document cannot show one - Exit statuses and the "unchanged files are not differences" rule are pinned by unit tests in
test/diff.test.ts;test/e2e.shgains eight cases covering the clean tree, a difference,--filenarrowing, the redirected-output case and both failure statuses
- The
CONTRIBUTING.md(#52) - the setup facts that were previously only discoverable by reading the workflow file:npm cirather thannpm install, the exact Node floor rather than the major, whytest/e2e*.shsits outsidenpm test, and how to run the e2e suite against your own project- Records two repository behaviors that look like a broken pull request and are not: CI from a first-time contributor waits at
action_requireduntil a maintainer approves it, and "Update branch" only appears when the branch merges cleanly ⚠️ test/e2e-ignore.shandtest/e2e-issue7.shhave a project ID hardcoded and only run against that one project. Documented rather than changed, since making them configurable is a code change
- Records two repository behaviors that look like a broken pull request and are not: CI from a first-time contributor waits at
docs/ARCHITECTURE.md(#53) - why the client looks the way it does. There is no public Overleaf API, so it authenticates as a logged-in browser session and calls the endpoints the web editor's own JavaScript calls- The numbered fallbacks in
extractCsrfTokenandlistProjectsare successive Overleaf redesigns rather than defensive clutter, andclient.tsnow says so at the lines themselves uploadFilereplaces whole files rather than sending edit operations, which is whypushhas no merge semantics and whydiffneeded to exist at all- The module map is organized by whether something needs an Overleaf account, since that is the question that decides where new logic goes
- Corrects the
client.tsfile header, which claimed to provide programmatic access to Overleaf's REST APIs
- The numbered fallbacks in
SECURITY.md- a private route for credential bugs. Private vulnerability reporting is enabled on the repository, so the file points there first and gives an email address as the fallback. It also records which parts of the credential handling are deliberate: the config directory holds plain JSON by design andolcli checkprints its path, so only credentials appearing somewhere else are a defectCITATION.cff- makes the package citable through GitHub's own citation control rather than leaving a reader to assemble the fields frompackage.json
olcli logoutleft.olauthbehind and reported success anyway (#50) - it cleared the global config and printedCredentials cleared, while the.olauthfile in the current directory survived. That file is consulted ahead of the global config, so the user stayed authenticated in that directory - and inolcli-mcp, which reads it too.logoutnow clears both and lists what it actually removed- Environment variables cannot be unset by a child process, so
OVERLEAF_SESSIONandOVERLEAF_EMAIL/OVERLEAF_PASSWORDare now reported instead of ignored. They outrank everything on disk, and a logout that stays silent about them repeats the original mistake in a different place
- Environment variables cannot be unset by a child process, so
olcli authclaimedPassword login saved.even under--no-save-password- the same class of bug: a message stating an outcome that did not happen. It now reports what was actually stored
- The account password is no longer persisted by default (#50) - it is written only when you ask for it with
--save-password. The session cookie is stored either way and is what every later command uses; the password only bought an automatic re-login after that cookie expired. A cookie is scoped to olcli and rotates, a password is reusable everywhere and cannot be revoked without changing it--no-save-passwordstill parses and still means "do not save", so existing scripts keep working - it is simply the default now- Behavior change for self-hosted users: an expired session no longer re-logs in silently. Re-run
olcli auth, pass--save-passwordto keep the old behavior, or setOVERLEAF_EMAIL/OVERLEAF_PASSWORD
olcli auth --passwordis now optional and prompts instead (#50) - passing it puts the password in shell history, soolcli auth --email you@example.comnow reads it from the terminal without echoing. The flag still works and warns; with no terminal available, the error namesOVERLEAF_EMAIL/OVERLEAF_PASSWORD, which every command already reads- Keystroke handling is a pure reducer so it can be tested without a pty. Driving the real prompt over one is what surfaced the bug it now guards: filtering only the ESC of an arrow key left the printable
[andAbehind and silently appended them to the password
- Keystroke handling is a pure reducer so it can be tested without a pty. Driving the real prompt over one is what surfaced the bug it now guards: filtering only the ESC of an arrow key left the printable
olcli checknow reports whether a password is stored and whether a.olauthfile is present, never the values. Answering "is my password on disk?" previously meant opening the config file
- CI status badge in the README, reporting what
ci.ymlhas been running since 0.10.0 - Spelling normalized to American English across the changelog and
docs/ARCHITECTURE.md:behaviour,organised,colourized,normalisation. Identifiers insrc/are untouched -colourizeDiffLineand thecolourlocals are code, and renaming them belongs in a change that touches that code anyway CONTRIBUTING.mdnow states the spelling rule rather than leaving it to be discovered in review, and namesCITATION.cffas the third place a release bumps alongsidepackage.jsonandpackage-lock.json. It is the one with no test behind it
olcli project create <name>(#47) - create a blank or example Overleaf project from the command line--template blank|examplepicks an empty project or Overleaf's example document,--jsonprints the created project for scripting- The new project becomes the remembered project, so a following
pullorpushneeds no argument OverleafClient.createProject()exposes the same thing through the programmatic API, withProjectTemplate,CreateProjectOptionsandCreatedProjectexported from the package root- Tested against a local HTTP server that captures the outgoing request, so request construction, response mapping, validation and failure paths are covered without an Overleaf account
create_projectMCP tool - the same thing for AI assistants, so one asked to start a new paper does not have to shell out to the CLI- Applies rather than previews, unlike
plan_project_renames. That one is plan-only because a bulk rename across an account is unrecoverable; creating is additive, touches nothing existing, and an unwanted project can be deleted afterwards - Returns the created project plus the template used, so a caller can tell a blank project from an example one without asking again
- Registration verified over a real MCP handshake:
tools/listreturns 19 tools includingcreate_project
- Applies rather than previews, unlike
- The documented global config path was wrong on macOS. The README named
~/.config/olcli-nodejs/config.json, butconfresolves to~/Library/Preferences/olcli-nodejs/config.jsonthere. The path is platform-dependent and is no longer hardcoded in the docs;olcli checkandolcli authboth print the real one
- @mohamedsobhi777 -
olcli project create(#47)
diff_projectMCP tool - the MCP counterpart ofolcli diff, so an assistant can preview what a push would change without shelling out to the CLI. Read-only: nothing is uploaded or written- Returns one entry per changed file with
path,status,binaryand a unifiedpatch, rather than one block of diff text. The other 17 tools return JSON and an agent should be able to filter by status without parsing output name_onlydrops the patch text,filerestricts to one path,contextsets hunk width, and both ignore switches mirror the CLI flags- Same semantics as the command: the remote is fetched fresh on every call and
remote_fetched_atis part of the response, because a collaborator editing between the call and a later push can still change the outcome - Registration verified over a real MCP handshake rather than by reading the source:
tools/listreturns 18 tools includingdiff_project, withproject_idandlocal_dirrequired
- Returns one entry per changed file with
olcli diff [project] [dir](#45) - content-level preview of what a push would changepush --dry-runanswers which files; there was no way to see what changed inside them short of pulling into a scratch directory and runningdiff(1)by hand- Unified diff to stdout, colorized when stdout is a TTY.
--name-onlyfor paths only,--file <path>for a single file,-U <n>for context width - The remote side is fetched fresh on every run, and the command says so in
--helpand in its output footer..olcli.jsonrecords remote paths, never remote contents, so there is no stored snapshot to compare against - "diff against the last pull" would have meant inventing a content cache, not reusing one. Fetching fresh is also what makes the diff describe what a subsequentpushwill overwrite, which is the question the command exists to answer - Cost of fetching fresh is one request:
downloadProjectreturns the whole project as a single archive, the same callpullandsyncalready make. Per-file fetching would have been one request per file and still could not have identified which files differ without downloading them a/is the remote andb/is local, so a+line is contentpushwould upload and a-line is content it would overwrite- Binary files (PDFs, images) are reported as
Binary files ... differ, detected by a NUL byte in the first 8000 bytes. No attempt is made to be cleverer - Both sides pass through the same ignore layers and the same dotfile rule. Filtering only the local side would have listed
output.pdfand every stray.auxon Overleaf as a local deletion on every run - Remote-only files are reported but flagged as untouched by a plain
push, since onlypush --deleteremoves them - Archive entries whose names escape the target directory are dropped, consistent with what
pullrefuses to extract
- Continuous integration for pull requests
- The repository had no CI for pull requests. Only
publish.ymlexisted, triggered by tags, so a change was first executed by a machine other than the author's at release time .github/workflows/ci.ymlrunsnpm ci, lint, build and test on pull requests and on pushes tomain, across Node 20.18.1 and 24- A final step verifies
dist/cli.js,dist/mcp.js,dist/remote-helper.jsanddist/index.jsare non-empty and thatnode dist/cli.js --versionruns.tscexiting zero does not prove entry points were emitted, and this is the step that caught the Node 18 breakage above on its first run test/e2e*.share deliberately excluded: they drive a real Overleaf account
- The repository had no CI for pull requests. Only
- A working
npm run lint(#46). The script had been defined since the initial release with noeslintindevDependenciesand no configuration, so it failed on every clean install. Addseslint9 withtypescript-eslint, flat config, no type-checked rulesno-explicit-anyis a warning rather than an error. 58 pre-existing occurrences sit where untyped JSON comes back from Overleaf, which publishes no schema for those responses. As an error, CI would be red onmainfrom the day it was switched on
- Local file scanning extracted into
src/scan.ts.pushandsynceach carried their own copy of the same walk-and-filter loop and the two had already drifted (syncguarded against a missing directory,pushdid not);diffwould have made a third. Same reasoning assrc/rename-plan.tsin 0.9.0 push --dry-runnow notes that its list is selected by modification time and points atolcli difffor content changes. The two commands answer different questions and will disagree - a file touched but not edited appears inpush --dry-runand not indiff- so the overlap is resolved by making each one say what it measures rather than by merging them
enginesclaimed Node 18 support that did not exist. olcli would not start at all on Node 18:client.tsimportscheerioat module load,cheerio@1.2.0depends onundici@7.x, and undici referencesFileas a global, which Node only exposes from 20 onwards. The process died withReferenceError: File is not definedbefore printing anything, including--version- Published 0.9.1 declared
engines: { node: ">=18" }while its own lockfile resolvedcheerio 1.2.0andundici 7.20.0, both declaring>=20.18.1. The manifest and the dependency tree contradicted each other enginesis now>=20.18.1, the exact floor everyundicirelease in the^7.19.0range requires. Not>=20: the floor is a patch version, and rounding it down would restate the same kind of claim this release is fixing- Node 18 reached end of life on 2025-04-30. Pinning
cheerioback to~1.1.0to keep it was considered and rejected: it freezes a dependency that would need manual attention on every future update, and itsundici@^7.10.0range was never measured to actually work on 18 - Verified against real Node 20.18.1 and 20.20.2 binaries, not against a version string:
npm ci, lint, build, tests andnode dist/cli.js --versionall pass, anddist/client.jsimports without error
- Published 0.9.1 declared
- Removed dead code the new lint setup surfaced:
printFolderincli.ts, unreachable since the initial 0.1.0 release and only ever calling itself; five imports that were pulled in and never referenced; twelvecatch (e)clauses that never read the binding; threeletbindings never reassigned. No behavior change docs/MCP.mdandSKILL.mdlisted 15 and 17 MCP tools against 18 registered.rename_projectandplan_project_renameshad been missing since they were added; both files are now checked against the registrations rather than maintained by hand
- New runtime dependency:
diff^9.0.0, which has no dependencies of its own - Comparison, rendering and remote-tree filtering live in
src/diff.tsas pure functions, so they are unit-tested without an Overleaf account (npm test). Likerename-plan.ts, they are not re-exported from the package root latexdiffintegration (--latexdiff,--pdf) is deliberately left out of this change and will follow separately
- Zip-slip path traversal when extracting project archives (#44, reported and fixed by @Waynting)
pullandsyncjoined each archive entry name onto the target directory with no validation, so an entry named../../../../home/user/.bashrcwould be written outside the project directory- adm-zip's own
extractAllTo()guards against this, but olcli extracts manually viaentry.getData()andwriteFileSync(), which bypasses it - This matters because olcli supports self-hosted Overleaf and ShareLaTeX instances, so the archive does not always come from a server the user controls or trusts
- New
resolveWithin(baseDir, relativePath)insrc/paths.tsresolves a candidate path against the base directory and returnsnullunless the result is strictly inside it. Rejects..escapes, absolute paths, Windows drive letters, the base directory itself, and sibling-prefix cases where/tmp/project-evilstring-prefixes/tmp/project pullskips unsafe entries with a warning and excludes them from theremoteManifestwritten to.olcli.json, so they cannot enter deletion propagation on a later syncsyncfilters them when building its remote file map and re-checks in the write loop- Well-formed archives are unaffected: safe entry names resolve to exactly the paths they did before
npm testscript runningtest/paths.test.tsvianode:testand the existingtsxdev dependency.publish.ymlalready callednpm test --if-present, which was a no-op until now, so this turns it into a real gate before publish. No new dependencies
olcli push --delete- opt-in propagation of local deletions to the remotepushhas never removed remote files, so a file deleted locally stayed on Overleaf indefinitely.syncalready handles this (#7), butsyncpulls the remote over the working tree first, which is not acceptable when the local tree is the source of truth- Deletion candidates come from
pushManifestin.olcli.json(what this directory last uploaded), never from the remote listing - files uploaded by collaborators through the web editor are left alone - Skipped entirely when no baseline manifest exists (first push from a directory), since "deleted locally" and "never existed here" cannot be told apart
- Deletions run after uploads, so a rename never leaves the remote without the file
- Opt-in: default
pushbehavior is unchanged
olcli project rename <newname> [project]- rename the project itselfolcli renametargets a doc/file/folder inside a project; there was no way to rename the project--dry-runprints the change without applying it- Renaming to the current name is a no-op with an info message, not an error
olcli project rename-bulk- pattern-based rename across many projects- Filters with
--match <regex>, transforms with--search/--replace,--prefix,--suffix - Dry-run is the default.
--applyis required to change anything - inverted from the usual convention because Overleaf keeps no project-name history, so a bulk rename fired on a typo cannot be undone --max <n>refuses to apply when more thannprojects would change- Collision detection: refuses to apply when two projects would end up with the same name, or when a target name is already taken by an untouched project. Overleaf tolerates duplicate names, so without this check the operation would succeed silently and leave projects that cannot be told apart in any listing
- A failure mid-run is reported and the remaining renames continue; a partial run is recoverable by re-running, aborting midway would leave the same partial state with no report
- Filters with
- MCP tool
rename_project- rename a project through the MCP surface - MCP tool
plan_project_renames- preview a bulk rename; returns planned renames, skipped projects and collisions- Deliberately plan-only. There is no apply counterpart on the MCP surface: an account-wide rename is unrecoverable, so applying a plan requires a human running
olcli project rename-bulk --apply
- Deliberately plan-only. There is no apply counterpart on the MCP surface: an account-wide rename is unrecoverable, so applying a plan requires a human running
- Rename planning logic extracted into
src/rename-plan.tsas a pure function, so the CLI and the MCP server evaluate the same rules. Two copies would drift, and the collision check is the part that must not
.olcli.jsongains an optionalpushManifestfield. Older versions ignore unknown fields, so downgrading is safe. When nopushManifestexists,push --deletefalls back to theremoteManifestwritten bypull
- Compile a specific
.texfile as the root document (#38) - contributed by @SomeBottle- New
-r, --resource <path>option oncompile,pdf, andoutput - Sets
rootResourcePathon the Overleaf compile endpoint, so multi-document projects can buildappendix.tex, a checklist document, or any other file independently - Useful for pulling a per-document
.bblwhen preparing arXiv bundles - MCP tools
compile,download_pdf, andcompile_with_outputsaccept an optionalresource_path - Clearer error when a compile fails and a resource path was given, hinting that the file may not exist in the project
- E2E coverage for compile, PDF download, output listing, and log download with
--resource
- New
--to <path>option onolcli upload(#40, closes #39) to state the remote destination explicitly
- Absolute paths passed to
olcli uploadno longer mirror the local directory structure into the project (#39) - reported by @SomeBottle while working on #38/tmp/tmp.abc123/paper.texpreviously uploaded totmp/tmp.abc123/paper.tex; it now lands in the project root aspaper.tex- Relative paths are unchanged, so
figures/diagram.pngstill lands in thefiguresfolder .and..segments are normalized, and paths that would escape the project root are collapsed rather than passed through- MCP
push_fileuses the same resolution forremote_path, so CLI and MCP agree - Path logic extracted into
src/paths.ts
- Git remote helper (#36) — use Overleaf projects as native git remotes with
git clone overleaf::<url>,git push, andgit pull. Implements thefast-import/fast-exportprotocol with mark-based incremental tracking. Newgit-remote-overleafbinary registered in package. Originally proposed in #15 by @bicheTortue. - Supports custom/self-hosted instances via URL:
git clone overleaf::https://overleaf.example.com/project/<id> - Auth via
OVERLEAF_SESSIONenv var,~/.olauth, or stored config - Debug mode via
GIT_REMOTE_OVERLEAF_DEBUG=1
- README restructured — extracted MCP docs to
docs/MCP.mdand git remote docs todocs/GIT-REMOTE.md - Landing page (
docs/index.html) updated with dynamic badges, git remote feature, and MCP section - SKILL.md updated with git remote, MCP, comments, and "when to use which mode" decision table
- Configurable HTTP timeout (#35, closes #30) — contributed by @rarensu
- Global
--timeoutCLI option OVERLEAF_TIMEOUTenvironment variableolcli config set-timeout/olcli config get-timeoutto persist- Precedence: flag > env > config > default (10000ms)
- Fixes network timeouts for users behind proxies or with slow connections (#29)
- Global
- Reply to comment threads (#34, closes #33) — contributed by @rarensu
olcli comments reply [project]- New
reply_to_commentMCP tool for AI agents
- Password login for self-hosted instances (#32) — contributed by @Li4nx
olcli auth --email --password ***- Auto-refreshes expired sessions using stored credentials
OverleafClient.fromPasswordLogin()static factory method- MCP server automatically falls back to password login when cookie expires
- Ideal for self-hosted Overleaf/ShareLaTeX without reCAPTCHA
- Session cookie persistence improvements (#31) — contributed by @Li4nx
getSessionCookiePair()for robust cookie name detection- Credential persistence helpers exported via library API
- Supports varied cookie names across Overleaf versions (
overleaf_session2,overleaf.sid,sharelatex.sid)
- @rarensu (Richard Lawrence) — timeout configuration, comment replies
- @Li4nx — password login, session persistence
- Library export — olcli is now dual-use (CLI and importable library):
All types and interfaces exported with full TypeScript declarations.
import { OverleafClient } from '@aloth/olcli'; const client = await OverleafClient.fromSessionCookie(cookie);
- MCP server (Model Context Protocol) — new
olcli-mcpbinary with 14 tools for AI agents (Claude Desktop, Cursor, Windsurf):list_projects,get_project_info,get_entitiespull_project,push_file,download_filecompile,download_pdf,compile_with_outputslist_comments,add_comment,resolve_commentdelete_entity,rename_entity
- CLI works identically to v0.4.1
- All existing scripts and workflows continue as-is
- PDF output selection (#26, thanks @drgmr!) —
olcli pdfnow correctly downloads the main compile output (output.pdf) instead of potentially picking up figure PDFs or*-eps-converted-to.pdfintermediates that appear earlier in the output file list.
- Review comment management (#25) — thanks @shiquda! 🎉
olcli comments list— view comments with source file, line/column, selected text, messages, and optional contextolcli comments add— attach a comment to selected text or an explicit source rangeolcli comments resolve/reopen/delete— manage comment threads- Supports
--status open|resolved|all,--context N, and--jsonoutput
- Nix flake (#24) — install via
nix profile install github:aloth/olcli
olcli pdf/olcli output <type>returnedDownload failed: 404(#22) — Overleaf's CDN now requires?clsiserverid=<id>on every build-output download. The compile response'sclsiServerIdis now appended to all output URLs.olcli upload figures/fig01.pngplaced the file in project root instead of insidefigures/. The CLI now preserves the relative path, and the folder tree is loaded (and cached) on demand. Subfolders are auto-created if missing.olcli syncupload pass had the same subfolder bug — fixed by the same self-healing change.
- Global
--verboseflag (#21) — prints every HTTP request, status, content-type, and (on non-2xx) a snippet of the response body to stderr. Works before or after the command name.
- New
OverleafClient.getOrLoadFolderTree(projectId)/invalidateFolderTree(projectId)helpers with per-project caching.
pushandsyncnow filter local files through a built-in ignore list before uploading to Overleaf. LaTeX build artifacts (.aux,.bbl,.log,.out,.fls,.fdb_latexmk,.synctex.gz, beamer/biber/glossaries/minted intermediates, etc.) and OS noise (.DS_Store,Thumbs.db,*.swp) are no longer uploaded.- PDF special rule:
X.pdfis ignored only if a same-namedX.tex(or.ltx) exists in the same folder. - To restore old behavior:
--no-default-ignoreor--no-ignore.
- PDF special rule:
.olignorefile support — gitignore-style syntax for project-level ignore patterns. Negation (!important.aux) supported..olignore.localfile support — machine-specific patterns.olcli ignored [dir]— lists all ignore patterns in effect, grouped by source.push --no-default-ignore/sync --no-default-ignorepush --no-ignore/sync --no-ignorepush --show-ignored/sync --show-ignored
- #19 —
syncno longer uploads LaTeX build artifacts that break Overleaf compile.
- New module
src/ignore.tswithDEFAULT_IGNORE_PATTERNS,loadIgnore(),shouldIgnore(), andbuildTexSiblingSet(). - New e2e test
test/e2e-ignore.sh(31 assertions).
syncis now destructive in both directions: files deleted locally are propagated to the remote on the next sync.- On first run after upgrade,
syncrecords a manifest of remote files in.olcli.json. From then on, any tracked file missing locally is deleted on Overleaf. - Use
sync --no-deleteto opt out per-run, orsync --dry-run --verboseto preview.
- On first run after upgrade,
delete/rmcommand — delete a file or folder by pathrename/mvcommand — rename a file or folder by pathsync --no-deleteflag.olcli.jsonnow stores amanifestfield (used for deletion detection)
- #7 —
syncno longer resurrects locally deleted files getProjectInfo()now falls back to the Socket.IOjoinProjectResponsewhen Overleaf's HTML no longer ships the project tree in<meta>tags.httpRequest()now serializesFormDatabodies properly.
- New e2e test
test/e2e-issue7.sh(22 assertions).
- Replace
fetchwith a shared Node http/https client for Overleaf requests - Fix ByteString/header failures caused by non-Latin1 response headers on additional request paths
- Support for self-hosted Overleaf / ShareLaTeX instances
- Configurable
--base-urland--cookie-name olcli config set-url/olcli config get-urlolcli config set-cookie-name/olcli config get-cookie-name
- Preserve folder structure when pushing nested files
- Generated
output.pdfis no longer pushed back to Overleaf duringpush/sync
- @admirkadriu — preserve folder structure when pushing files (#3)
- @bicheTortue — README improvements (#9), avoid re-uploading generated PDF (#10)
- @Alice-space — self-hosted Overleaf support (#11)
- Fix ByteString error for projects with non-ASCII names (#2) — binary downloads (
pull,pdf,output) now use Node.js nativehttp/httpsmodules which don't have the Latin1 header restriction.
- Root folder ID resolution now uses Overleaf's collaboration socket payload as authoritative source, fixing
pushfailures (folder_not_found) (#1) uploadFile()now auto-retries once with a refreshed root folder ID when receivingfolder_not_found
- E2E tests are now portable across projects
- @vicmcorrea — first community contribution!
- Improved npm SEO with enhanced description and keywords
- Improved README for SEO and clarity
- Folder resolution for imported Overleaf projects (
folder_not_founderrors) - Trusted publishing workflow for npm
- Demo GIF in README
- Dynamic version reading from package.json