overleaf-codex 0.1.0-rc.1

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.

Potentially problematic release.


This version of overleaf-codex might be problematic. Click here for more details.

Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +25 -0
  3. package/README.md +217 -0
  4. package/assets/olcx-mark.svg +22 -0
  5. package/dist/auth/projectAuth.d.ts +19 -0
  6. package/dist/auth/projectAuth.js +163 -0
  7. package/dist/auth/projectAuth.js.map +1 -0
  8. package/dist/auth/redact.d.ts +3 -0
  9. package/dist/auth/redact.js +7 -0
  10. package/dist/auth/redact.js.map +1 -0
  11. package/dist/auth/types.d.ts +10 -0
  12. package/dist/auth/types.js +4 -0
  13. package/dist/auth/types.js.map +1 -0
  14. package/dist/backend/index.d.ts +6 -0
  15. package/dist/backend/index.js +2 -0
  16. package/dist/backend/index.js.map +1 -0
  17. package/dist/backend/olcli/client.d.ts +329 -0
  18. package/dist/backend/olcli/client.js +1757 -0
  19. package/dist/backend/olcli/client.js.map +1 -0
  20. package/dist/backend/olcli/index.d.ts +2 -0
  21. package/dist/backend/olcli/index.js +2 -0
  22. package/dist/backend/olcli/index.js.map +1 -0
  23. package/dist/backend/overleafBackend.d.ts +41 -0
  24. package/dist/backend/overleafBackend.js +200 -0
  25. package/dist/backend/overleafBackend.js.map +1 -0
  26. package/dist/backend/types.d.ts +73 -0
  27. package/dist/backend/types.js +2 -0
  28. package/dist/backend/types.js.map +1 -0
  29. package/dist/cli-behavior.d.ts +14 -0
  30. package/dist/cli-behavior.js +59 -0
  31. package/dist/cli-behavior.js.map +1 -0
  32. package/dist/cli.d.ts +30 -0
  33. package/dist/cli.js +441 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/commands/auth.d.ts +21 -0
  36. package/dist/commands/auth.js +104 -0
  37. package/dist/commands/auth.js.map +1 -0
  38. package/dist/commands/compile.d.ts +7 -0
  39. package/dist/commands/compile.js +73 -0
  40. package/dist/commands/compile.js.map +1 -0
  41. package/dist/commands/doctor.d.ts +11 -0
  42. package/dist/commands/doctor.js +9 -0
  43. package/dist/commands/doctor.js.map +1 -0
  44. package/dist/commands/endpoint.d.ts +23 -0
  45. package/dist/commands/endpoint.js +69 -0
  46. package/dist/commands/endpoint.js.map +1 -0
  47. package/dist/commands/init.d.ts +14 -0
  48. package/dist/commands/init.js +48 -0
  49. package/dist/commands/init.js.map +1 -0
  50. package/dist/commands/status.d.ts +4 -0
  51. package/dist/commands/status.js +5 -0
  52. package/dist/commands/status.js.map +1 -0
  53. package/dist/commands/sync.d.ts +26 -0
  54. package/dist/commands/sync.js +139 -0
  55. package/dist/commands/sync.js.map +1 -0
  56. package/dist/commands/watch.d.ts +28 -0
  57. package/dist/commands/watch.js +124 -0
  58. package/dist/commands/watch.js.map +1 -0
  59. package/dist/compile/compileFlow.d.ts +32 -0
  60. package/dist/compile/compileFlow.js +290 -0
  61. package/dist/compile/compileFlow.js.map +1 -0
  62. package/dist/compile/pdfOutput.d.ts +12 -0
  63. package/dist/compile/pdfOutput.js +64 -0
  64. package/dist/compile/pdfOutput.js.map +1 -0
  65. package/dist/config/ignoreRules.d.ts +5 -0
  66. package/dist/config/ignoreRules.js +53 -0
  67. package/dist/config/ignoreRules.js.map +1 -0
  68. package/dist/config/overleafProject.d.ts +9 -0
  69. package/dist/config/overleafProject.js +61 -0
  70. package/dist/config/overleafProject.js.map +1 -0
  71. package/dist/config/projectConfig.d.ts +6 -0
  72. package/dist/config/projectConfig.js +180 -0
  73. package/dist/config/projectConfig.js.map +1 -0
  74. package/dist/config/projectRoot.d.ts +1 -0
  75. package/dist/config/projectRoot.js +36 -0
  76. package/dist/config/projectRoot.js.map +1 -0
  77. package/dist/config/types.d.ts +50 -0
  78. package/dist/config/types.js +34 -0
  79. package/dist/config/types.js.map +1 -0
  80. package/dist/config/vscode.d.ts +10 -0
  81. package/dist/config/vscode.js +134 -0
  82. package/dist/config/vscode.js.map +1 -0
  83. package/dist/diagnostics/doctor.d.ts +8 -0
  84. package/dist/diagnostics/doctor.js +209 -0
  85. package/dist/diagnostics/doctor.js.map +1 -0
  86. package/dist/diagnostics/status.d.ts +6 -0
  87. package/dist/diagnostics/status.js +110 -0
  88. package/dist/diagnostics/status.js.map +1 -0
  89. package/dist/diagnostics/types.d.ts +33 -0
  90. package/dist/diagnostics/types.js +2 -0
  91. package/dist/diagnostics/types.js.map +1 -0
  92. package/dist/endpoint/overleafEndpoint.d.ts +36 -0
  93. package/dist/endpoint/overleafEndpoint.js +105 -0
  94. package/dist/endpoint/overleafEndpoint.js.map +1 -0
  95. package/dist/errors.d.ts +32 -0
  96. package/dist/errors.js +53 -0
  97. package/dist/errors.js.map +1 -0
  98. package/dist/sync/apply.d.ts +14 -0
  99. package/dist/sync/apply.js +92 -0
  100. package/dist/sync/apply.js.map +1 -0
  101. package/dist/sync/conflicts.d.ts +7 -0
  102. package/dist/sync/conflicts.js +59 -0
  103. package/dist/sync/conflicts.js.map +1 -0
  104. package/dist/sync/ignore.d.ts +5 -0
  105. package/dist/sync/ignore.js +74 -0
  106. package/dist/sync/ignore.js.map +1 -0
  107. package/dist/sync/plan.d.ts +3 -0
  108. package/dist/sync/plan.js +197 -0
  109. package/dist/sync/plan.js.map +1 -0
  110. package/dist/sync/snapshot.d.ts +13 -0
  111. package/dist/sync/snapshot.js +82 -0
  112. package/dist/sync/snapshot.js.map +1 -0
  113. package/dist/sync/state.d.ts +16 -0
  114. package/dist/sync/state.js +214 -0
  115. package/dist/sync/state.js.map +1 -0
  116. package/dist/sync/types.d.ts +113 -0
  117. package/dist/sync/types.js +4 -0
  118. package/dist/sync/types.js.map +1 -0
  119. package/dist/testing/fakeBackend.d.ts +27 -0
  120. package/dist/testing/fakeBackend.js +213 -0
  121. package/dist/testing/fakeBackend.js.map +1 -0
  122. package/dist/watch/queue.d.ts +2 -0
  123. package/dist/watch/queue.js +91 -0
  124. package/dist/watch/queue.js.map +1 -0
  125. package/dist/watch/types.d.ts +52 -0
  126. package/dist/watch/types.js +2 -0
  127. package/dist/watch/types.js.map +1 -0
  128. package/dist/watch/watcher.d.ts +6 -0
  129. package/dist/watch/watcher.js +58 -0
  130. package/dist/watch/watcher.js.map +1 -0
  131. package/dist/watch/workflow.d.ts +30 -0
  132. package/dist/watch/workflow.js +62 -0
  133. package/dist/watch/workflow.js.map +1 -0
  134. package/docs/architecture.md +603 -0
  135. package/docs/auth.md +65 -0
  136. package/docs/cli-behavior.md +95 -0
  137. package/docs/compile.md +51 -0
  138. package/docs/design.md +82 -0
  139. package/docs/endpoint.md +84 -0
  140. package/docs/npm-packaging.md +148 -0
  141. package/docs/quickdev-queue-audit.md +193 -0
  142. package/docs/release-gates.md +119 -0
  143. package/docs/release-notes-v1.md +97 -0
  144. package/docs/security.md +61 -0
  145. package/docs/sync-state.md +305 -0
  146. package/docs/sync.md +50 -0
  147. package/docs/troubleshooting.md +124 -0
  148. package/docs/usage.md +184 -0
  149. package/examples/minimal-paper/.olcx/auth.local.example.json +7 -0
  150. package/examples/minimal-paper/.olcx/config.json +23 -0
  151. package/examples/minimal-paper/README.md +88 -0
  152. package/examples/minimal-paper/main.tex +23 -0
  153. package/package.json +66 -0
  154. package/src/backend/olcli/LICENSE +21 -0
  155. package/src/backend/olcli/README.md +26 -0
@@ -0,0 +1,119 @@
1
+ # Release Gates
2
+
3
+ Run this before publishing:
4
+
5
+ ```bash
6
+ npm run prepublish:check
7
+ ```
8
+
9
+ The command runs build, typecheck, the deterministic test suite, a high-severity
10
+ npm audit, dependency license validation, olcli notice validation, a
11
+ sensitive-value scan over release-relevant files, and an npm pack dry-run
12
+ package-content check.
13
+
14
+ The release checklist is:
15
+
16
+ ```bash
17
+ npm run build
18
+ npm run typecheck
19
+ npm test
20
+ OLCX_E2E_IGNORE_LOCAL_ENV=1 OLCX_E2E_ENABLE_REAL=0 npm run test:e2e:real
21
+ npm audit --audit-level=high
22
+ npm pack --dry-run --json --ignore-scripts
23
+ npm run prepublish:check
24
+ ```
25
+
26
+ Before freezing a v1 RC, update [docs/release-notes-v1.md](release-notes-v1.md)
27
+ with the final gate results and stable-release decision. Stable release is not approved until a sanitized disposable real Overleaf E2E pass is recorded. The default agent-safe gate may only run the forced skip smoke:
28
+
29
+ ```bash
30
+ OLCX_E2E_IGNORE_LOCAL_ENV=1 OLCX_E2E_ENABLE_REAL=0 npm run test:e2e:real
31
+ ```
32
+
33
+ The forced real E2E skip smoke must use `OLCX_E2E_IGNORE_LOCAL_ENV=1` so it does
34
+ not read a contributor's ignored `.env.e2e.local` file. Do not run real Overleaf
35
+ E2E unless you intentionally set `OLCX_E2E_ENABLE_REAL=1` with a disposable,
36
+ sanitized test project.
37
+
38
+ ## npm Trusted Publishing Gate
39
+
40
+ The npm publishing workflow is `.github/workflows/npm-publish.yml`. It publishes
41
+ only from explicit GitHub release publication events and uses the protected
42
+ GitHub environment `npm-publish`. Configure that environment with manual
43
+ reviewer protection before enabling real publication.
44
+
45
+ GitHub release tags must match the package version:
46
+
47
+ - Stable releases use tags like `vX.Y.Z`, non-prerelease GitHub releases, and
48
+ npm dist-tag `latest`.
49
+ - Prereleases use tags like `vX.Y.Z-rc.1`, GitHub prereleases, and npm dist-tag
50
+ `next`.
51
+
52
+ Stable npm publish is blocked until both conditions are true:
53
+
54
+ - `docs/release-notes-v1.md` records stable release approval.
55
+ - A sanitized disposable real Overleaf E2E artifact is recorded and reviewed
56
+ with this concrete reference format:
57
+
58
+ ```text
59
+ Sanitized real E2E artifact: gh-release://umiskky/overleaf-codex/vX.Y.Z/sanitized-real-e2e.md
60
+ ```
61
+
62
+ Concrete sanitized real E2E artifact reference means the referenced release
63
+ artifact has been reviewed and contains no raw cookie, session value, account
64
+ label, private project id, or private paper content.
65
+
66
+ The forced skip smoke is allowed but is not a stable substitute. It remains
67
+ required because it proves the CI E2E command is safely disabled and does not
68
+ read ignored local environment files.
69
+
70
+ ## Package Contents
71
+
72
+ The package allowlist is intentionally small: `assets/`, `dist/`, `docs/`,
73
+ `examples/`, root `README.md`, root `LICENSE`, root `NOTICE.md`,
74
+ `package.json`, `src/backend/olcli/LICENSE`, and
75
+ `src/backend/olcli/README.md`.
76
+
77
+ Inspect the package surface directly with:
78
+
79
+ ```bash
80
+ npm pack --dry-run --json --ignore-scripts
81
+ ```
82
+
83
+ The gate fails if the dry-run package contains `node_modules/`, tests, scripts,
84
+ real `.olcx/` local state, local or secret JSON, local environment files,
85
+ `tmp/`, generated Overleaf output, E2E output, or logs. The only tracked
86
+ `.olcx` files allowed in the package are
87
+ `examples/minimal-paper/.olcx/config.json` and
88
+ `examples/minimal-paper/.olcx/auth.local.example.json`; both must stay
89
+ sanitized placeholders.
90
+
91
+ ## Dependency Licenses
92
+
93
+ The current lockfile uses these compatible license families: `0BSD`,
94
+ `Apache-2.0`, `BSD-2-Clause`, `BSD-3-Clause`, `ISC`, `MIT`, and `MPL-2.0`.
95
+
96
+ `MPL-2.0` appears only as dependency/dev-tool package metadata and does not
97
+ relicense `olcx` source. Do not add GPL, AGPL, LGPL, unknown, or missing-license
98
+ dependencies without a documented legal review and an explicit gate update.
99
+
100
+ ## npm Audit
101
+
102
+ The release gate runs:
103
+
104
+ ```bash
105
+ npm audit --audit-level=high
106
+ ```
107
+
108
+ There are no active audit exceptions. If a future high-severity advisory cannot
109
+ be fixed before release, document the package, advisory id, severity, runtime
110
+ reachability, mitigation, owner, and expiration date in this file before
111
+ changing the gate.
112
+
113
+ ## Third-Party Source Notices
114
+
115
+ `olcx` vendors backend-private source copied or adapted from
116
+ `@aloth/olcli@0.5.0`. Preserve the MIT license text in
117
+ `src/backend/olcli/LICENSE`, the source metadata in `NOTICE.md`, `README.md`,
118
+ and `src/backend/olcli/README.md`, and the attribution header in
119
+ `src/backend/olcli/client.ts`.
@@ -0,0 +1,97 @@
1
+ # v1 Release Notes
2
+
3
+ Release candidate status: Local release-candidate gates passed; stable release remains blocked until sanitized disposable real Overleaf E2E is recorded.
4
+
5
+ Stable release decision: Not approved for stable release until a sanitized disposable real Overleaf E2E pass is recorded.
6
+
7
+ `olcx` is not an official Overleaf project and is not an official `olcli` project. It is not affiliated with, endorsed by, or maintained by Overleaf or `olcli`.
8
+
9
+ ## What v1 Includes
10
+
11
+ - CLI-first paper workflow through `olcx auth`, `olcx init`, `olcx endpoint status`, `olcx endpoint test`, `olcx status`, `olcx doctor`, `olcx sync`, `olcx compile`, and `olcx watch`.
12
+ - One local paper repository binds to one Overleaf project by default.
13
+ - Manual Overleaf endpoint management for `https://www.overleaf.com` and
14
+ `https://cn.overleaf.com`, with read-only probing and explicit
15
+ `olcx endpoint test --apply` selection.
16
+ - Project-local auth stored in `.olcx/auth.local.json`, which must remain ignored by Git.
17
+ - Safe sync planning and conflict stop behavior with `SYNC_CONFLICT`; sync must not silently overwrite local or remote changes.
18
+ - Remote Overleaf-backed compile that writes the PDF to `build/overleaf/main.pdf` by default and does not require local LaTeX.
19
+ - VS Code settings/tasks generated by `olcx init` by default; v1 does not include a VS Code extension.
20
+ - Backend-private code copied or adapted from `@aloth/olcli@0.5.0` with MIT attribution preserved.
21
+
22
+ ## Final Gate Checklist
23
+
24
+ These gates must pass before an RC can be considered locally verified:
25
+
26
+ ```bash
27
+ npm run build
28
+ npm run typecheck
29
+ npm test
30
+ OLCX_E2E_IGNORE_LOCAL_ENV=1 OLCX_E2E_ENABLE_REAL=0 npm run test:e2e:real
31
+ npm audit --audit-level=high
32
+ npm pack --dry-run --json --ignore-scripts
33
+ npm run prepublish:check
34
+ npm run dev -- --help
35
+ ```
36
+
37
+ `npm pack --dry-run --json --ignore-scripts` must match the `package.json` `files` allowlist and must not include local auth, local or secret JSON, environment files, `tmp/`, tests, scripts, generated PDFs, logs, `node_modules/`, or real E2E output.
38
+
39
+ ## npm Publishing Status
40
+
41
+ Stable npm publish is blocked. The repository contains
42
+ `.github/workflows/npm-publish.yml` for GitHub Actions Trusted Publishing through
43
+ the protected `npm-publish` environment, but the current v1 notes do not approve
44
+ a stable npm release.
45
+
46
+ Release tags must match `package.json`:
47
+
48
+ - `vX.Y.Z-rc.1` style prerelease versions must use GitHub prereleases and npm
49
+ dist-tag `next`.
50
+ - `vX.Y.Z` stable versions must use non-prerelease GitHub releases and npm
51
+ dist-tag `latest` only after stable approval.
52
+
53
+ Sanitized real E2E artifact: not recorded for this release candidate. This line
54
+ is a placeholder for the future sanitized artifact reference and is not evidence
55
+ of a completed real E2E run.
56
+
57
+ Future stable approval must replace the placeholder with this concrete format:
58
+
59
+ ```text
60
+ Sanitized real E2E artifact: gh-release://umiskky/overleaf-codex/vX.Y.Z/sanitized-real-e2e.md
61
+ ```
62
+
63
+ The forced skip smoke is allowed but is not a stable substitute. It only proves
64
+ the CI command is safely disabled for agent-safe release-candidate checks.
65
+
66
+ ## Real Overleaf E2E Policy
67
+
68
+ The default release-candidate verification runs only the forced skip smoke:
69
+
70
+ ```bash
71
+ OLCX_E2E_IGNORE_LOCAL_ENV=1 OLCX_E2E_ENABLE_REAL=0 npm run test:e2e:real
72
+ ```
73
+
74
+ This command must not read `.env.e2e.local` and must not contact Overleaf.
75
+
76
+ A stable release requires a separate disposable real Overleaf E2E pass. The recorded artifact must be sanitized and must state:
77
+
78
+ - the command completed successfully against a disposable project;
79
+ - no raw cookie, session value, account label, private project id, or private paper content is recorded;
80
+ - the artifact path is a sanitized handoff or release-management artifact, not a packaged npm file.
81
+
82
+ No raw real-E2E credentials, project IDs, cookies, or private paper contents may be committed.
83
+
84
+ ## Known limitations
85
+
86
+ - Overleaf access depends on Overleaf private interfaces and may break when Overleaf changes its web or compile behavior.
87
+ - Authentication uses a session cookie or environment-provided token-like value; v1 must not store Overleaf passwords.
88
+ - JSON output mode is reserved for a future release.
89
+ - v1 is a CLI release. A VS Code extension is intentionally out of scope.
90
+ - Real E2E is gated because it needs disposable Overleaf credentials and a disposable Overleaf project.
91
+
92
+ ## Post-v1 roadmap
93
+
94
+ - Add a stable release process after sanitized disposable real E2E is recorded.
95
+ - Improve structured output for automation.
96
+ - Expand compatibility coverage as Overleaf behavior changes.
97
+ - Consider a VS Code extension only after the CLI workflow is stable.
@@ -0,0 +1,61 @@
1
+ # Security
2
+
3
+ `olcx` handles Overleaf authorization data. Treat that data as secret.
4
+
5
+ ## Storage model
6
+
7
+ Authorization is project-local by default:
8
+
9
+ ```text
10
+ .olcx/auth.local.json
11
+ ```
12
+
13
+ This supports the common case where different paper repositories use different
14
+ Overleaf accounts.
15
+
16
+ Project binding and workflow settings live in:
17
+
18
+ ```text
19
+ .olcx/config.json
20
+ ```
21
+
22
+ `config.json` is designed to be shareable, but it may still reveal an Overleaf
23
+ project id. Users should decide whether their paper repository is private.
24
+
25
+ ## Git rules
26
+
27
+ The following files must never be committed:
28
+
29
+ ```text
30
+ .olcx/auth.local.json
31
+ .olcx/*.local.json
32
+ .olcx/*.secret.json
33
+ ```
34
+
35
+ Generated PDFs and LaTeX build artifacts are ignored by default:
36
+
37
+ ```text
38
+ build/overleaf/
39
+ *.aux
40
+ *.log
41
+ *.synctex.gz
42
+ ```
43
+
44
+ ## Passwords
45
+
46
+ The first version must not store Overleaf passwords. Auth should use a session
47
+ cookie or environment-provided token-like value.
48
+
49
+ ## Headless usage
50
+
51
+ If a server has no browser, the expected flow is:
52
+
53
+ 1. Log in to Overleaf from any browser.
54
+ 2. Copy the required session value.
55
+ 3. Provide it to `olcx auth` or an environment variable on the server.
56
+ 4. Store it in the paper repository's ignored local auth file.
57
+
58
+ ## Reporting
59
+
60
+ `olcx status` may show the account label or email when available, but it must not
61
+ print the raw session cookie.
@@ -0,0 +1,305 @@
1
+ # Sync State
2
+
3
+ ## Purpose And Scope
4
+
5
+ `olcx sync` and `olcx watch` use one shared state machine for file comparison,
6
+ ignore handling, delete safety, conflict semantics, and recovery guidance. Watch
7
+ does not define a second conflict model; it pauses on the same conflicts that a
8
+ manual sync reports.
9
+
10
+ This document defines planning, local snapshots, and conflict reporting. It does
11
+ not implement real Overleaf calls, backend adapters, filesystem reads, filesystem
12
+ writes, or command wiring.
13
+
14
+ ## Local State Files
15
+
16
+ The sync state file is:
17
+
18
+ ```text
19
+ .olcx/state/sync.json
20
+ ```
21
+
22
+ It stores the last successful bidirectional sync baseline used by sync and watch.
23
+ It is local-only state, must remain ignored by Git, and is not shareable project
24
+ configuration.
25
+
26
+ The conflict report file is:
27
+
28
+ ```text
29
+ .olcx/state/conflicts.json
30
+ ```
31
+
32
+ It stores the latest local diagnostic report for a paused sync/watch flow. It is
33
+ not a source of file truth and must not be committed.
34
+
35
+ Neither file may contain authorization data, credentials, session values,
36
+ passwords, cookies, file contents, private compile logs, raw backend responses,
37
+ or private paper content.
38
+
39
+ ## Content Digest
40
+
41
+ Content digests use SHA-256 and are encoded as lowercase hexadecimal strings.
42
+ The digest is computed over exact bytes. If a caller has a string, it hashes
43
+ `Buffer.from(value)` with the default UTF-8 encoding. If a caller has bytes, it
44
+ hashes those bytes directly.
45
+
46
+ Line endings are not normalized before hashing. This keeps the baseline honest:
47
+ if local bytes differ from remote bytes, the hashes differ.
48
+
49
+ Remote digests may use backend-provided hashes only when the backend hash is
50
+ semantically the same content digest. If not, a later backend task must download
51
+ the bytes and hash them before planning.
52
+
53
+ ## Path Normalization
54
+
55
+ All sync paths are repository-relative POSIX-style paths with forward slashes.
56
+ Inputs such as `./main.tex` normalize to `main.tex`, and Windows separators are
57
+ converted to `/`.
58
+
59
+ Absolute paths and parent traversal are not valid sync targets. Paths such as
60
+ `/tmp/main.tex`, `../main.tex`, and `sections/../main.tex` must not escape the
61
+ paper repository. A pure planner may treat them as safe ignored inputs or as
62
+ unsupported conflicts, but it must never upload, download, delete, or watch them.
63
+
64
+ ## State Schema
65
+
66
+ The persisted state shape is:
67
+
68
+ ```json
69
+ {
70
+ "schemaVersion": 1,
71
+ "hashAlgorithm": "sha256",
72
+ "updatedAt": "2026-06-25T08:00:00.000Z",
73
+ "files": {
74
+ "main.tex": {
75
+ "path": "main.tex",
76
+ "contentHash": "<sha256-hex>",
77
+ "size": 1234,
78
+ "localModifiedAt": "2026-06-25T07:59:00.000Z",
79
+ "remoteModifiedAt": "2026-06-25T07:58:00.000Z",
80
+ "remoteId": "<remote-file-id>",
81
+ "remoteRevision": "<remote-revision>",
82
+ "syncedAt": "2026-06-25T08:00:00.000Z"
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ `schemaVersion` is `1` for the v1 state file. `hashAlgorithm` is `sha256`.
89
+ `updatedAt` is the time the state file was last replaced after a successful
90
+ non-dry-run sync.
91
+
92
+ Each file entry stores the repository-relative path, content hash, optional size,
93
+ optional local and remote modified times, optional remote identity metadata, and
94
+ the time that path was last known to be synchronized.
95
+
96
+ ## Remote And Local Snapshot Fields
97
+
98
+ Local snapshots use these fields:
99
+
100
+ | Field | Meaning |
101
+ | --- | --- |
102
+ | `path` | Repository-relative POSIX path. |
103
+ | `exists` | Whether the local file exists at snapshot time. |
104
+ | `contentHash` | SHA-256 digest when the file exists. |
105
+ | `size` | File size in bytes when available. |
106
+ | `modifiedAt` | Local modified timestamp when available. |
107
+ | `ignored` | Whether the path was already classified as ignored by the caller. |
108
+
109
+ Remote snapshots use these fields:
110
+
111
+ | Field | Meaning |
112
+ | --- | --- |
113
+ | `path` | Repository-relative POSIX path. |
114
+ | `exists` | Whether the remote file exists at snapshot time. |
115
+ | `contentHash` | SHA-256 digest or equivalent backend content digest. |
116
+ | `size` | Remote file size in bytes when available. |
117
+ | `modifiedAt` | Remote modified timestamp when available. |
118
+ | `remoteId` | Backend file identifier for later operations. |
119
+ | `revision` | Backend revision marker for conflict diagnostics. |
120
+ | `binary` | Whether the remote entry should be treated as binary. |
121
+
122
+ Snapshots contain metadata only. They do not contain file bodies.
123
+
124
+ ## Ignore Precedence
125
+
126
+ Ignore handling is deterministic and shared by sync and watch.
127
+
128
+ 1. Normalize paths to repository-relative POSIX paths.
129
+ 2. Reject or safe-ignore absolute paths and paths containing `..`.
130
+ 3. Apply built-in safety ignores first.
131
+ 4. Apply user-configured ignores second.
132
+ 5. Do not support negated unignore rules such as `!path` in v1.
133
+
134
+ The built-in safety ignores are:
135
+
136
+ ```text
137
+ .git/
138
+ node_modules/
139
+ .olcx/auth.local.json
140
+ .olcx/*.local.json
141
+ .olcx/*.secret.json
142
+ .olcx/state/
143
+ build/overleaf/
144
+ *.aux
145
+ *.bbl
146
+ *.bcf
147
+ *.blg
148
+ *.fdb_latexmk
149
+ *.fls
150
+ *.log
151
+ *.out
152
+ *.run.xml
153
+ *.synctex.gz
154
+ *.toc
155
+ ```
156
+
157
+ User ignores are appended after the built-in set, so they can exclude more files
158
+ but cannot re-include a built-in safety ignore.
159
+
160
+ Ignored paths become `ignored` operations. They never upload, download, delete,
161
+ watch-trigger, or become blocking conflicts.
162
+
163
+ ## SyncPlan Operations
164
+
165
+ `SyncPlan` operations are:
166
+
167
+ | Operation | Meaning |
168
+ | --- | --- |
169
+ | `upload` | Local content is the only safe change and should be sent to remote during apply. |
170
+ | `download` | Remote content is the only safe change and should be written locally during apply. |
171
+ | `deleteLocal` | A remote deletion may remove the local file only in an explicit delete-allowed flow. |
172
+ | `deleteRemote` | A local deletion may remove the remote file only in an explicit delete-allowed flow. |
173
+ | `unchanged` | No apply action is needed for this path. |
174
+ | `conflict` | Automatic apply must pause for this path. |
175
+ | `ignored` | The path is excluded and must not be applied or reported as a conflict. |
176
+
177
+ The v1 default matrix is:
178
+
179
+ | Baseline | Local now | Remote now | Result |
180
+ | --- | --- | --- | --- |
181
+ | absent | present | absent | `upload` |
182
+ | absent | absent | present | `download` |
183
+ | absent | present hash A | present hash A | `unchanged` |
184
+ | absent | present hash A | present hash B | `conflict`, `both-modified` |
185
+ | present hash A | present hash A | present hash A | `unchanged` |
186
+ | present hash A | present hash B | present hash A | `upload` |
187
+ | present hash A | present hash A | present hash B | `download` |
188
+ | present hash A | present hash B | present hash B | `unchanged` |
189
+ | present hash A | present hash B | present hash C | `conflict`, `both-modified` |
190
+ | present hash A | present hash B | absent | `conflict`, `local-modified-remote-deleted` |
191
+ | present hash A | absent | present hash B | `conflict`, `remote-modified-local-deleted` |
192
+ | present hash A | present hash A | absent | default `conflict`, `unsafe-delete`; with `allowDeletes: true`, `deleteLocal` |
193
+ | present hash A | absent | present hash A | default `conflict`, `unsafe-delete`; with `allowDeletes: true`, `deleteRemote` |
194
+ | present hash A | absent | absent | `unchanged`, `both-deleted` |
195
+ | any | ignored | any | `ignored` |
196
+ | any | any | ignored | `ignored` |
197
+
198
+ Any non-empty `conflicts` list pauses automatic sync and watch application.
199
+
200
+ ## V1 Delete Policy
201
+
202
+ Automatic deletes are disabled by default in CLI v1. A missing file on one side
203
+ can mean a deliberate delete, a stale listing, a backend issue, or a local
204
+ mistake. The default planner therefore downgrades risky or implicit deletes to a
205
+ `conflict` with reason `unsafe-delete`.
206
+
207
+ `deleteLocal` and `deleteRemote` remain in the type contract for a future
208
+ explicit user-confirmed flow or a carefully gated internal `allowDeletes` mode.
209
+ The default CLI path must not pass `allowDeletes: true`.
210
+
211
+ ## Conflict Report Format
212
+
213
+ The conflict report path is:
214
+
215
+ ```text
216
+ .olcx/state/conflicts.json
217
+ ```
218
+
219
+ The report contains paths, content hashes, sizes, timestamps, known remote
220
+ metadata, suggested commands, watch pause state, and manual steps. It does not
221
+ include the raw project id because resolving conflicts does not require it.
222
+
223
+ Example:
224
+
225
+ ```json
226
+ {
227
+ "schemaVersion": 1,
228
+ "generatedAt": "2026-06-25T08:00:00.000Z",
229
+ "reportPath": ".olcx/state/conflicts.json",
230
+ "syncStatePath": ".olcx/state/sync.json",
231
+ "watch": {
232
+ "paused": true,
233
+ "reason": "sync-conflict",
234
+ "resumeCommand": "olcx watch"
235
+ },
236
+ "conflicts": [
237
+ {
238
+ "path": "main.tex",
239
+ "reason": "both-modified",
240
+ "local": {
241
+ "contentHash": "<local-sha256-hex>",
242
+ "size": 1234,
243
+ "modifiedAt": "2026-06-25T08:00:00.000Z"
244
+ },
245
+ "remote": {
246
+ "contentHash": "<remote-sha256-hex>",
247
+ "size": 1250,
248
+ "modifiedAt": "2026-06-25T08:01:00.000Z",
249
+ "remoteId": "<remote-file-id>",
250
+ "revision": "<remote-revision>"
251
+ },
252
+ "base": {
253
+ "contentHash": "<base-sha256-hex>",
254
+ "size": 1200,
255
+ "syncedAt": "2026-06-25T07:50:00.000Z"
256
+ },
257
+ "suggestedCommands": ["olcx sync --dry-run", "olcx sync"],
258
+ "manualSteps": [
259
+ "Review main.tex locally and in Overleaf.",
260
+ "Review both versions, merge manually, then run olcx sync --dry-run.",
261
+ "Run olcx sync --dry-run before applying changes."
262
+ ]
263
+ }
264
+ ],
265
+ "manualSteps": [
266
+ "Open each conflict path locally and in Overleaf.",
267
+ "Choose local, remote, or a manual merge.",
268
+ "Run olcx sync --dry-run.",
269
+ "Run olcx sync after the dry run is clean.",
270
+ "Restart olcx watch if you use the watcher."
271
+ ]
272
+ }
273
+ ```
274
+
275
+ Conflict reports must copy only known metadata fields. They must exclude full
276
+ file contents, cookies, passwords, session values, authorization data, CSRF
277
+ values, raw private logs, and raw backend responses. Formatting must pass
278
+ serialized output through the shared redaction helper before display or storage.
279
+
280
+ ## Snapshot Update Rules
281
+
282
+ - Do not update `.olcx/state/sync.json` during `--dry-run`.
283
+ - Do not update the snapshot when the plan contains any `conflict`.
284
+ - Do not update the snapshot when any apply step fails.
285
+ - Update the snapshot only after a complete successful non-dry-run sync apply.
286
+ - For `upload` or `download`, the new baseline digest is the resulting content
287
+ hash shared by both sides.
288
+ - For `unchanged`, keep or refresh metadata when local and remote hashes match.
289
+ - For `ignored`, do not persist ignored path entries.
290
+ - For successful user-confirmed deletes in a future flow, remove the deleted
291
+ path from the state.
292
+ - Never write auth data, cookies, session values, file contents, private logs, or
293
+ full remote responses into the state file.
294
+
295
+ ## Testing Contract
296
+
297
+ Sync state-machine tests cover pure functions with in-memory snapshots. They do
298
+ not use real Overleaf, local LaTeX, filesystem writes, backend calls, network
299
+ calls, Commander imports, or CLI wiring.
300
+
301
+ Required coverage includes local-only upload, remote-only download, unchanged
302
+ hashes, built-in ignores, user ignores, both-modified conflicts, local-modified
303
+ versus remote-deleted conflicts, remote-modified versus local-deleted conflicts,
304
+ default unsafe delete downgrades, explicit `allowDeletes` mode, dry-run flag
305
+ preservation, summary counts, and conflict report redaction.
package/docs/sync.md ADDED
@@ -0,0 +1,50 @@
1
+ # Sync
2
+
3
+ `olcx sync` synchronizes the local paper repository with the bound Overleaf
4
+ project. It must not silently overwrite local or remote changes.
5
+
6
+ ## Dry Run First
7
+
8
+ ```bash
9
+ olcx sync --dry-run
10
+ ```
11
+
12
+ The dry run prints planned uploads, downloads, and deletes without changing
13
+ files. Use it before any manual sync in a repository that may have local or
14
+ remote edits.
15
+
16
+ ## Apply A Clean Plan
17
+
18
+ ```bash
19
+ olcx sync
20
+ ```
21
+
22
+ `olcx sync` applies the plan only when it can do so safely. The sync state is
23
+ stored under `.olcx/state/` and generated reports are local-only.
24
+
25
+ ## Conflict Handling
26
+
27
+ If the same path changed locally and on Overleaf, `olcx` exits with
28
+ `SYNC_CONFLICT` and writes:
29
+
30
+ ```text
31
+ .olcx/state/conflicts.json
32
+ ```
33
+
34
+ Review each listed path locally and in Overleaf, choose the correct content or
35
+ perform a manual merge, then rerun:
36
+
37
+ ```bash
38
+ olcx sync --dry-run
39
+ cat .olcx/state/conflicts.json
40
+ olcx sync
41
+ ```
42
+
43
+ Do not delete the conflict report until you understand why the conflict
44
+ happened.
45
+ Do not commit `.olcx/state/` files. Conflict reports contain metadata and paths only; they must not contain cookies, auth values, raw backend responses, private logs, or paper content.
46
+
47
+ ## Watch Integration
48
+
49
+ `olcx watch` pauses when sync reports a conflict. Resolve the conflict manually,
50
+ confirm the dry run is clean, then restart the watcher.