joplin-md-sync 1.5.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. joplin_md_sync-1.5.5/.version +1 -0
  2. joplin_md_sync-1.5.5/LICENSE +21 -0
  3. joplin_md_sync-1.5.5/MANIFEST.in +10 -0
  4. joplin_md_sync-1.5.5/PKG-INFO +221 -0
  5. joplin_md_sync-1.5.5/README.md +178 -0
  6. joplin_md_sync-1.5.5/docs/contracts/AGENT_INTERFACES.md +129 -0
  7. joplin_md_sync-1.5.5/docs/contracts/CI_RELEASES.md +165 -0
  8. joplin_md_sync-1.5.5/docs/contracts/CLI.md +112 -0
  9. joplin_md_sync-1.5.5/docs/contracts/DEPENDENCIES.md +98 -0
  10. joplin_md_sync-1.5.5/docs/contracts/README.md +49 -0
  11. joplin_md_sync-1.5.5/docs/contracts/SECURITY.md +99 -0
  12. joplin_md_sync-1.5.5/docs/contracts/SERVICE.md +123 -0
  13. joplin_md_sync-1.5.5/docs/contracts/SYNCHRONIZATION.md +126 -0
  14. joplin_md_sync-1.5.5/docs/contracts/WORKSPACE.md +108 -0
  15. joplin_md_sync-1.5.5/docs/index.md +282 -0
  16. joplin_md_sync-1.5.5/docs/maintenance/ARCHITECTURE.md +129 -0
  17. joplin_md_sync-1.5.5/docs/maintenance/DEPENDENCIES.md +44 -0
  18. joplin_md_sync-1.5.5/docs/maintenance/DEVELOPMENT.md +65 -0
  19. joplin_md_sync-1.5.5/docs/maintenance/RELEASES.md +56 -0
  20. joplin_md_sync-1.5.5/docs/maintenance/SECURITY.md +58 -0
  21. joplin_md_sync-1.5.5/docs/maintenance/SERVICE.md +59 -0
  22. joplin_md_sync-1.5.5/docs/maintenance/SYNCHRONIZATION.md +59 -0
  23. joplin_md_sync-1.5.5/docs/user/AGENT_INTERFACES.md +88 -0
  24. joplin_md_sync-1.5.5/docs/user/AGENT_WORKFLOWS.md +161 -0
  25. joplin_md_sync-1.5.5/docs/user/CHATGPT_ACTIONS.md +242 -0
  26. joplin_md_sync-1.5.5/docs/user/CLI.md +173 -0
  27. joplin_md_sync-1.5.5/docs/user/CONFLICTS.md +40 -0
  28. joplin_md_sync-1.5.5/docs/user/GETTING_STARTED.md +174 -0
  29. joplin_md_sync-1.5.5/docs/user/MCP_API.md +166 -0
  30. joplin_md_sync-1.5.5/docs/user/OVERVIEW.md +102 -0
  31. joplin_md_sync-1.5.5/docs/user/SELF_HOSTED.md +122 -0
  32. joplin_md_sync-1.5.5/docs/user/SERVICE.md +929 -0
  33. joplin_md_sync-1.5.5/docs/user/WORKSPACE_FORMAT.md +72 -0
  34. joplin_md_sync-1.5.5/examples/agent-notes-repository/.gitignore +16 -0
  35. joplin_md_sync-1.5.5/examples/agent-notes-repository/AGENTS.md +222 -0
  36. joplin_md_sync-1.5.5/examples/agent-notes-repository/README.md +128 -0
  37. joplin_md_sync-1.5.5/examples/agent-notes-repository/RUNBOOK.md +166 -0
  38. joplin_md_sync-1.5.5/examples/agent-notes-repository/scripts/install-joplin-md-sync.py +322 -0
  39. joplin_md_sync-1.5.5/examples/agent-session.md +63 -0
  40. joplin_md_sync-1.5.5/examples/config.example.json +11 -0
  41. joplin_md_sync-1.5.5/examples/windows/install-service-task.ps1 +83 -0
  42. joplin_md_sync-1.5.5/pyproject.toml +89 -0
  43. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/collect_joplin_debug.sh +260 -0
  44. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/install_joplin_terminal.py +2868 -0
  45. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/joplin_terminal_common.py +255 -0
  46. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/run_joplin_terminal.py +383 -0
  47. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/systemd/joplin-md-sync.service +28 -0
  48. joplin_md_sync-1.5.5/scripts/joplin_terminal_service/systemd/joplin-terminal.service +22 -0
  49. joplin_md_sync-1.5.5/scripts/prepare_chatgpt_action.py +286 -0
  50. joplin_md_sync-1.5.5/setup.cfg +4 -0
  51. joplin_md_sync-1.5.5/src/joplin_md_sync/__init__.py +54 -0
  52. joplin_md_sync-1.5.5/src/joplin_md_sync/__main__.py +6 -0
  53. joplin_md_sync-1.5.5/src/joplin_md_sync/api.py +624 -0
  54. joplin_md_sync-1.5.5/src/joplin_md_sync/auth.py +146 -0
  55. joplin_md_sync-1.5.5/src/joplin_md_sync/canonical.py +81 -0
  56. joplin_md_sync-1.5.5/src/joplin_md_sync/cli.py +1332 -0
  57. joplin_md_sync-1.5.5/src/joplin_md_sync/config.py +130 -0
  58. joplin_md_sync-1.5.5/src/joplin_md_sync/conflicts.py +457 -0
  59. joplin_md_sync-1.5.5/src/joplin_md_sync/diff.py +185 -0
  60. joplin_md_sync-1.5.5/src/joplin_md_sync/errors.py +137 -0
  61. joplin_md_sync-1.5.5/src/joplin_md_sync/gpt_actions.py +577 -0
  62. joplin_md_sync-1.5.5/src/joplin_md_sync/gpt_openapi.py +244 -0
  63. joplin_md_sync-1.5.5/src/joplin_md_sync/journal.py +214 -0
  64. joplin_md_sync-1.5.5/src/joplin_md_sync/json_safety.py +30 -0
  65. joplin_md_sync-1.5.5/src/joplin_md_sync/locking.py +89 -0
  66. joplin_md_sync-1.5.5/src/joplin_md_sync/mcp_server.py +493 -0
  67. joplin_md_sync-1.5.5/src/joplin_md_sync/mcp_service.py +1139 -0
  68. joplin_md_sync-1.5.5/src/joplin_md_sync/metadata.py +130 -0
  69. joplin_md_sync-1.5.5/src/joplin_md_sync/models.py +246 -0
  70. joplin_md_sync-1.5.5/src/joplin_md_sync/paths.py +91 -0
  71. joplin_md_sync-1.5.5/src/joplin_md_sync/planner.py +760 -0
  72. joplin_md_sync-1.5.5/src/joplin_md_sync/resources.py +86 -0
  73. joplin_md_sync-1.5.5/src/joplin_md_sync/state.py +337 -0
  74. joplin_md_sync-1.5.5/src/joplin_md_sync/sync.py +774 -0
  75. joplin_md_sync-1.5.5/src/joplin_md_sync/tool_executor.py +113 -0
  76. joplin_md_sync-1.5.5/src/joplin_md_sync/tool_registry.py +693 -0
  77. joplin_md_sync-1.5.5/src/joplin_md_sync/tool_schema.py +207 -0
  78. joplin_md_sync-1.5.5/src/joplin_md_sync/update_check.py +94 -0
  79. joplin_md_sync-1.5.5/src/joplin_md_sync/workspace.py +338 -0
  80. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/PKG-INFO +221 -0
  81. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/SOURCES.txt +83 -0
  82. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/dependency_links.txt +1 -0
  83. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/entry_points.txt +2 -0
  84. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/requires.txt +22 -0
  85. joplin_md_sync-1.5.5/src/joplin_md_sync.egg-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ 1.5.5
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joplin-md-sync contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ include .version
2
+ recursive-include docs *.md
3
+ recursive-include examples *.json *.md *.ps1 *.py
4
+ include examples/agent-notes-repository/.gitignore
5
+ include scripts/prepare_chatgpt_action.py
6
+ include scripts/joplin_terminal_service/collect_joplin_debug.sh
7
+ include scripts/joplin_terminal_service/install_joplin_terminal.py
8
+ include scripts/joplin_terminal_service/joplin_terminal_common.py
9
+ include scripts/joplin_terminal_service/run_joplin_terminal.py
10
+ recursive-include scripts/joplin_terminal_service/systemd *.service
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: joplin-md-sync
3
+ Version: 1.5.5
4
+ Summary: Safe two-way synchronization between Joplin notes and a local Markdown workspace, built for autonomous coding agents
5
+ Author: joplin-md-sync contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://joplin-mcp.romancello.net/
8
+ Project-URL: Documentation, https://joplin-mcp.romancello.net/
9
+ Project-URL: Repository, https://github.com/kogeler/joplin-md-sync
10
+ Project-URL: Issues, https://github.com/kogeler/joplin-md-sync/issues
11
+ Project-URL: Changelog, https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md
12
+ Keywords: joplin,markdown,sync,notes,cli,agents
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.13
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: bandit[toml]==1.9.4; extra == "dev"
26
+ Requires-Dist: mypy==2.3.1; extra == "dev"
27
+ Requires-Dist: pip-audit==2.10.1; extra == "dev"
28
+ Requires-Dist: ruff==0.16.4; extra == "dev"
29
+ Provides-Extra: test
30
+ Requires-Dist: colorama==0.4.6; extra == "test"
31
+ Requires-Dist: pytest==9.1.1; extra == "test"
32
+ Requires-Dist: pytest-cov==7.1.0; extra == "test"
33
+ Requires-Dist: pytest-xdist==3.8.0; extra == "test"
34
+ Provides-Extra: package
35
+ Requires-Dist: build==1.5.0; extra == "package"
36
+ Requires-Dist: colorama==0.4.6; extra == "package"
37
+ Requires-Dist: pefile==2024.8.26; extra == "package"
38
+ Requires-Dist: pyinstaller==6.22.2; extra == "package"
39
+ Requires-Dist: pywin32-ctypes==0.2.3; extra == "package"
40
+ Provides-Extra: docs
41
+ Requires-Dist: mkdocs-material==9.7.7; extra == "docs"
42
+ Dynamic: license-file
43
+
44
+ # joplin-md-sync
45
+
46
+ Safe two-way synchronization between the local [Joplin](https://joplinapp.org/)
47
+ desktop application and an ordinary directory of Markdown files — built
48
+ primarily for **autonomous coding agents** (deterministic JSON output, stable
49
+ exit codes, explicit conflict handling), and perfectly usable by humans.
50
+
51
+ **Documentation:** [joplin-mcp.romancello.net](https://joplin-mcp.romancello.net/)
52
+
53
+ > **Safety first.** The tool never overwrites divergent edits, never deletes
54
+ > anything without an explicit flag, never uses permanent deletion in Joplin,
55
+ > verifies every write after applying it, and journals every mutating run so
56
+ > interrupted syncs are recoverable. `diff` never mutates anything.
57
+
58
+ The test-backed definitions of these guarantees live in the
59
+ [contract catalog](https://joplin-mcp.romancello.net/contracts/).
60
+
61
+ If you are an agent (or configuring one), start with
62
+ **[AGENTS.md](https://github.com/kogeler/joplin-md-sync/blob/main/AGENTS.md)**.
63
+
64
+ ## How it works
65
+
66
+ - Notes are plain `.md` files; each carries a one-line metadata header with
67
+ the Joplin id, title, and tags. Notebooks are directories with a
68
+ `.joplin-folder.json`.
69
+ - Sync state (base snapshots for true three-way comparison) lives in
70
+ `.joplin-sync/state.sqlite3` inside the workspace — never committed to Git.
71
+ - All communication uses the documented local
72
+ [Joplin Data API](https://joplinapp.org/help/api/references/rest_api/)
73
+ (Web Clipper service); Joplin's own database and sync targets are never
74
+ touched.
75
+
76
+ ## Installation
77
+
78
+ Source, wheel, and zipapp installations require CPython **>= 3.13** on
79
+ Windows or Linux. Native release executables include Python and have no
80
+ external runtime dependencies.
81
+
82
+ ```bash
83
+ python -m pip install "joplin-md-sync==1.5.5"
84
+ # or: pipx install "joplin-md-sync==1.5.5"
85
+ # or download joplin-md-sync.pyz from a release and: python joplin-md-sync.pyz --help
86
+ ```
87
+
88
+ Native GitHub Release assets:
89
+
90
+ | Platform | Architecture | Asset |
91
+ | --- | --- | --- |
92
+ | Linux | AMD64 | `joplin-md-sync-linux-amd64` |
93
+ | Linux | ARM64 | `joplin-md-sync-linux-arm64` |
94
+ | Windows | AMD64 | `joplin-md-sync-windows-amd64.exe` |
95
+
96
+ On Linux, mark the downloaded executable as executable before running it:
97
+
98
+ ```bash
99
+ chmod +x joplin-md-sync-linux-amd64
100
+ ./joplin-md-sync-linux-amd64 version
101
+ ```
102
+
103
+ From a checkout, everything is driven by the Makefile:
104
+
105
+ ```bash
106
+ make venv # runtime venv/ with the CLI installed (venv/bin/joplin-md-sync)
107
+ make venv-dev # Linux quality tools (ruff, mypy, Bandit, pip-audit)
108
+ make venv-test # cross-platform pytest tools
109
+ make venv-package # cross-platform PyInstaller and build tools
110
+ make check # lint + typecheck + full test suite
111
+ make freeze-check # verify all generated dependency locks are current
112
+ make test-live # opt-in real-Joplin MCP + GPT Actions suites; reads ./token
113
+ make test TEST_WORKERS=8 # override automatic parallel test workers
114
+ make package # wheel, sdist, .pyz, current-platform executable, checksums
115
+ make help # all targets
116
+ ```
117
+
118
+ The version's single source is the root `.version` file; runtime
119
+ dependencies are declared in `pyproject.toml` (none by design). Direct tools are
120
+ pinned to their latest stable compatible releases in purpose-specific quality,
121
+ test, package, and docs groups. The four committed `pip-compile` locks include
122
+ SHA-256 hashes and are checked for drift in CI; Windows jobs install only the
123
+ cross-platform test or package lock they need.
124
+
125
+ ## Five-minute quick start
126
+
127
+ 1. In Joplin: *Tools > Options > Web Clipper* — enable the service, copy the
128
+ authorization token.
129
+ 2. ```bash
130
+ export JOPLIN_TOKEN=<your token> # Windows: set JOPLIN_TOKEN=...
131
+ joplin-md-sync init --root ./notes
132
+ joplin-md-sync pull --root ./notes
133
+ ```
134
+ The token is the only required configuration: the default endpoint
135
+ `http://127.0.0.1:41184` is built in (override with `JOPLIN_BASE_URL`,
136
+ `JOPLIN_PORT`, `--base-url`, or `--port` when needed).
137
+ 3. Edit files under `./notes`, then:
138
+ ```bash
139
+ joplin-md-sync diff --root ./notes
140
+ joplin-md-sync push --root ./notes --dry-run
141
+ joplin-md-sync push --root ./notes
142
+ ```
143
+ 4. If both sides changed the same note, you get exit code 2 and a conflict
144
+ bundle: `joplin-md-sync conflicts list` / `conflicts resolve ID --take-local|--take-remote|--merged-file PATH`.
145
+
146
+ ## Agent notes repository template
147
+
148
+ [`examples/agent-notes-repository/`](https://github.com/kogeler/joplin-md-sync/tree/main/examples/agent-notes-repository) is a
149
+ copyable starter repository for users who want an agent to work on their
150
+ Joplin notes as ordinary Markdown files. It includes:
151
+
152
+ - a repository-specific `AGENTS.md` with the guarded pull/edit/diff/dry-run/push
153
+ workflow and an optional MCP setup path;
154
+ - a human runbook that starts with enabling Web Clipper in Joplin Desktop and
155
+ storing its token outside the sync workspace;
156
+ - a `.gitignore` for credentials, the local standalone binary, and all sync
157
+ state; and
158
+ - a standard-library installer that downloads the latest compatible native
159
+ release into `.tools/`, verifies `SHA256SUMS.txt`, and validates the binary
160
+ before replacing an existing installation.
161
+
162
+ Start with the template's [README](https://github.com/kogeler/joplin-md-sync/blob/main/examples/agent-notes-repository/README.md),
163
+ not the development instructions for this source checkout.
164
+
165
+ ## MCP and ChatGPT Actions service
166
+
167
+ One foreground `joplin-md-sync` process exposes MCP and authenticated REST
168
+ Actions from the same tool registry on different URIs. It starts while Joplin
169
+ is offline and recovers on later calls without a restart.
170
+
171
+ The Actions token is mandatory when Actions are enabled and is reloaded from a
172
+ protected file. MCP bearer authentication remains optional through a separate
173
+ `--auth-token-file`. The headless installer always installs both APIs as one
174
+ `joplin-md-sync.service`; it generates separate mandatory Actions and MCP
175
+ tokens, stores them in protected files, and reports only their paths after a
176
+ successful install. It never creates a separate Actions service.
177
+
178
+ Use [service installation and operations](https://joplin-mcp.romancello.net/user/SERVICE/) for Linux,
179
+ Windows, credentials, URI isolation, and live tests;
180
+ [MCP API](https://joplin-mcp.romancello.net/user/MCP_API/) for the
181
+ tool contract; and the single [ChatGPT Actions end-to-end
182
+ setup](https://joplin-mcp.romancello.net/user/CHATGPT_ACTIONS/) for endpoint validation, schema export, GPT
183
+ Instructions, editor configuration, and acceptance testing.
184
+
185
+ ## Architecture overview
186
+
187
+ ```
188
+ cli -> planner (pure three-way classification: base/local/remote)
189
+ -> executor (guard -> apply -> verify -> commit base, journaled)
190
+ api: stdlib urllib client for the Joplin Data API (pagination, retries)
191
+ mcp/actions: two HTTP transports -> shared registry/executor -> Joplin service -> api
192
+ state: SQLite base snapshots, conflicts, tombstones, run journal
193
+ workspace: scanning, atomic writes, backups, quarantine, cross-platform lock
194
+ ```
195
+
196
+ Details in [Architecture](https://joplin-mcp.romancello.net/maintenance/ARCHITECTURE/).
197
+
198
+ ## Supported / not supported (v1)
199
+
200
+ | Supported | Out of scope |
201
+ | --- | --- |
202
+ | notes, notebooks (nested), tags, binary attachments | editing settings, revisions, or encryption state |
203
+ | two-way sync with conflict bundles | Nextcloud/WebDAV or any direct sync target |
204
+ | resource download/upload/edit through MCP | replacing Joplin's own device sync |
205
+ | crash recovery, backups, quarantine | automatic text merging (only explicit `--merged-file`) |
206
+ | Windows + Linux, Python 3.13/3.14; MCP daemon | filesystem watch mode, native mobile CLI, self-update, permanent note/notebook deletion |
207
+
208
+ ## Versioning
209
+
210
+ Semantic versioning; Git tags `vX.Y.Z` with GitHub releases carrying the
211
+ wheel, sdist, `.pyz`, native executables, and SHA-256 checksums.
212
+ The same wheel and sdist are published to PyPI through Trusted Publishing.
213
+ `joplin-md-sync update-check --json` compares the installed version against
214
+ the latest stable release
215
+ (exit 8 when outdated). JSON output, exit codes, and the state schema are
216
+ versioned and stable across patch releases. See
217
+ [CHANGELOG.md](https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md).
218
+
219
+ ## License
220
+
221
+ [MIT](https://github.com/kogeler/joplin-md-sync/blob/main/LICENSE).
@@ -0,0 +1,178 @@
1
+ # joplin-md-sync
2
+
3
+ Safe two-way synchronization between the local [Joplin](https://joplinapp.org/)
4
+ desktop application and an ordinary directory of Markdown files — built
5
+ primarily for **autonomous coding agents** (deterministic JSON output, stable
6
+ exit codes, explicit conflict handling), and perfectly usable by humans.
7
+
8
+ **Documentation:** [joplin-mcp.romancello.net](https://joplin-mcp.romancello.net/)
9
+
10
+ > **Safety first.** The tool never overwrites divergent edits, never deletes
11
+ > anything without an explicit flag, never uses permanent deletion in Joplin,
12
+ > verifies every write after applying it, and journals every mutating run so
13
+ > interrupted syncs are recoverable. `diff` never mutates anything.
14
+
15
+ The test-backed definitions of these guarantees live in the
16
+ [contract catalog](https://joplin-mcp.romancello.net/contracts/).
17
+
18
+ If you are an agent (or configuring one), start with
19
+ **[AGENTS.md](https://github.com/kogeler/joplin-md-sync/blob/main/AGENTS.md)**.
20
+
21
+ ## How it works
22
+
23
+ - Notes are plain `.md` files; each carries a one-line metadata header with
24
+ the Joplin id, title, and tags. Notebooks are directories with a
25
+ `.joplin-folder.json`.
26
+ - Sync state (base snapshots for true three-way comparison) lives in
27
+ `.joplin-sync/state.sqlite3` inside the workspace — never committed to Git.
28
+ - All communication uses the documented local
29
+ [Joplin Data API](https://joplinapp.org/help/api/references/rest_api/)
30
+ (Web Clipper service); Joplin's own database and sync targets are never
31
+ touched.
32
+
33
+ ## Installation
34
+
35
+ Source, wheel, and zipapp installations require CPython **>= 3.13** on
36
+ Windows or Linux. Native release executables include Python and have no
37
+ external runtime dependencies.
38
+
39
+ ```bash
40
+ python -m pip install "joplin-md-sync==1.5.5"
41
+ # or: pipx install "joplin-md-sync==1.5.5"
42
+ # or download joplin-md-sync.pyz from a release and: python joplin-md-sync.pyz --help
43
+ ```
44
+
45
+ Native GitHub Release assets:
46
+
47
+ | Platform | Architecture | Asset |
48
+ | --- | --- | --- |
49
+ | Linux | AMD64 | `joplin-md-sync-linux-amd64` |
50
+ | Linux | ARM64 | `joplin-md-sync-linux-arm64` |
51
+ | Windows | AMD64 | `joplin-md-sync-windows-amd64.exe` |
52
+
53
+ On Linux, mark the downloaded executable as executable before running it:
54
+
55
+ ```bash
56
+ chmod +x joplin-md-sync-linux-amd64
57
+ ./joplin-md-sync-linux-amd64 version
58
+ ```
59
+
60
+ From a checkout, everything is driven by the Makefile:
61
+
62
+ ```bash
63
+ make venv # runtime venv/ with the CLI installed (venv/bin/joplin-md-sync)
64
+ make venv-dev # Linux quality tools (ruff, mypy, Bandit, pip-audit)
65
+ make venv-test # cross-platform pytest tools
66
+ make venv-package # cross-platform PyInstaller and build tools
67
+ make check # lint + typecheck + full test suite
68
+ make freeze-check # verify all generated dependency locks are current
69
+ make test-live # opt-in real-Joplin MCP + GPT Actions suites; reads ./token
70
+ make test TEST_WORKERS=8 # override automatic parallel test workers
71
+ make package # wheel, sdist, .pyz, current-platform executable, checksums
72
+ make help # all targets
73
+ ```
74
+
75
+ The version's single source is the root `.version` file; runtime
76
+ dependencies are declared in `pyproject.toml` (none by design). Direct tools are
77
+ pinned to their latest stable compatible releases in purpose-specific quality,
78
+ test, package, and docs groups. The four committed `pip-compile` locks include
79
+ SHA-256 hashes and are checked for drift in CI; Windows jobs install only the
80
+ cross-platform test or package lock they need.
81
+
82
+ ## Five-minute quick start
83
+
84
+ 1. In Joplin: *Tools > Options > Web Clipper* — enable the service, copy the
85
+ authorization token.
86
+ 2. ```bash
87
+ export JOPLIN_TOKEN=<your token> # Windows: set JOPLIN_TOKEN=...
88
+ joplin-md-sync init --root ./notes
89
+ joplin-md-sync pull --root ./notes
90
+ ```
91
+ The token is the only required configuration: the default endpoint
92
+ `http://127.0.0.1:41184` is built in (override with `JOPLIN_BASE_URL`,
93
+ `JOPLIN_PORT`, `--base-url`, or `--port` when needed).
94
+ 3. Edit files under `./notes`, then:
95
+ ```bash
96
+ joplin-md-sync diff --root ./notes
97
+ joplin-md-sync push --root ./notes --dry-run
98
+ joplin-md-sync push --root ./notes
99
+ ```
100
+ 4. If both sides changed the same note, you get exit code 2 and a conflict
101
+ bundle: `joplin-md-sync conflicts list` / `conflicts resolve ID --take-local|--take-remote|--merged-file PATH`.
102
+
103
+ ## Agent notes repository template
104
+
105
+ [`examples/agent-notes-repository/`](https://github.com/kogeler/joplin-md-sync/tree/main/examples/agent-notes-repository) is a
106
+ copyable starter repository for users who want an agent to work on their
107
+ Joplin notes as ordinary Markdown files. It includes:
108
+
109
+ - a repository-specific `AGENTS.md` with the guarded pull/edit/diff/dry-run/push
110
+ workflow and an optional MCP setup path;
111
+ - a human runbook that starts with enabling Web Clipper in Joplin Desktop and
112
+ storing its token outside the sync workspace;
113
+ - a `.gitignore` for credentials, the local standalone binary, and all sync
114
+ state; and
115
+ - a standard-library installer that downloads the latest compatible native
116
+ release into `.tools/`, verifies `SHA256SUMS.txt`, and validates the binary
117
+ before replacing an existing installation.
118
+
119
+ Start with the template's [README](https://github.com/kogeler/joplin-md-sync/blob/main/examples/agent-notes-repository/README.md),
120
+ not the development instructions for this source checkout.
121
+
122
+ ## MCP and ChatGPT Actions service
123
+
124
+ One foreground `joplin-md-sync` process exposes MCP and authenticated REST
125
+ Actions from the same tool registry on different URIs. It starts while Joplin
126
+ is offline and recovers on later calls without a restart.
127
+
128
+ The Actions token is mandatory when Actions are enabled and is reloaded from a
129
+ protected file. MCP bearer authentication remains optional through a separate
130
+ `--auth-token-file`. The headless installer always installs both APIs as one
131
+ `joplin-md-sync.service`; it generates separate mandatory Actions and MCP
132
+ tokens, stores them in protected files, and reports only their paths after a
133
+ successful install. It never creates a separate Actions service.
134
+
135
+ Use [service installation and operations](https://joplin-mcp.romancello.net/user/SERVICE/) for Linux,
136
+ Windows, credentials, URI isolation, and live tests;
137
+ [MCP API](https://joplin-mcp.romancello.net/user/MCP_API/) for the
138
+ tool contract; and the single [ChatGPT Actions end-to-end
139
+ setup](https://joplin-mcp.romancello.net/user/CHATGPT_ACTIONS/) for endpoint validation, schema export, GPT
140
+ Instructions, editor configuration, and acceptance testing.
141
+
142
+ ## Architecture overview
143
+
144
+ ```
145
+ cli -> planner (pure three-way classification: base/local/remote)
146
+ -> executor (guard -> apply -> verify -> commit base, journaled)
147
+ api: stdlib urllib client for the Joplin Data API (pagination, retries)
148
+ mcp/actions: two HTTP transports -> shared registry/executor -> Joplin service -> api
149
+ state: SQLite base snapshots, conflicts, tombstones, run journal
150
+ workspace: scanning, atomic writes, backups, quarantine, cross-platform lock
151
+ ```
152
+
153
+ Details in [Architecture](https://joplin-mcp.romancello.net/maintenance/ARCHITECTURE/).
154
+
155
+ ## Supported / not supported (v1)
156
+
157
+ | Supported | Out of scope |
158
+ | --- | --- |
159
+ | notes, notebooks (nested), tags, binary attachments | editing settings, revisions, or encryption state |
160
+ | two-way sync with conflict bundles | Nextcloud/WebDAV or any direct sync target |
161
+ | resource download/upload/edit through MCP | replacing Joplin's own device sync |
162
+ | crash recovery, backups, quarantine | automatic text merging (only explicit `--merged-file`) |
163
+ | Windows + Linux, Python 3.13/3.14; MCP daemon | filesystem watch mode, native mobile CLI, self-update, permanent note/notebook deletion |
164
+
165
+ ## Versioning
166
+
167
+ Semantic versioning; Git tags `vX.Y.Z` with GitHub releases carrying the
168
+ wheel, sdist, `.pyz`, native executables, and SHA-256 checksums.
169
+ The same wheel and sdist are published to PyPI through Trusted Publishing.
170
+ `joplin-md-sync update-check --json` compares the installed version against
171
+ the latest stable release
172
+ (exit 8 when outdated). JSON output, exit codes, and the state schema are
173
+ versioned and stable across patch releases. See
174
+ [CHANGELOG.md](https://github.com/kogeler/joplin-md-sync/blob/main/CHANGELOG.md).
175
+
176
+ ## License
177
+
178
+ [MIT](https://github.com/kogeler/joplin-md-sync/blob/main/LICENSE).
@@ -0,0 +1,129 @@
1
+ <!-- Copyright (c) 2026 kogeler. SPDX-License-Identifier: MIT. -->
2
+
3
+ # Agent Interface Contract
4
+
5
+ ## Assertions
6
+
7
+ ### `AIF-001` - One immutable registry owns every agent operation
8
+
9
+ **Contract:** MCP and GPT Actions MUST derive tool names, schemas, effects,
10
+ handlers, and exposure from one ordered immutable registry and invoke handlers
11
+ through one validating executor. HTTP adapters MUST NOT maintain a second
12
+ operation list or bypass schema validation and failure classification.
13
+
14
+ **Evidence:**
15
+
16
+ - [`test_registry_is_ordered_exact_and_deeply_immutable`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_registry_is_ordered_exact_and_deeply_immutable`
17
+ - [`test_effect_and_route_are_centralized`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_effect_and_route_are_centralized`
18
+ - [`test_executor_validates_and_preserves_domain_errors`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_executor_validates_and_preserves_domain_errors`
19
+ - [`test_generated_operations_match_exposed_registry`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_generated_operations_match_exposed_registry`
20
+
21
+ ### `AIF-002` - MCP follows the advertised Streamable HTTP lifecycle
22
+
23
+ **Contract:** `mcp serve` MUST expose MCP Streamable HTTP on loopback
24
+ `/mcp` by default, negotiate only its supported protocol versions, implement
25
+ initialize/initialized, tools/list, and tools/call, and return both text content
26
+ and `structuredContent`. The server MUST advertise itself through CLI
27
+ capabilities and MUST NOT require a Markdown workspace.
28
+
29
+ **Evidence:**
30
+
31
+ - [`test_lifecycle_and_transport_contract`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_lifecycle_and_transport_contract`
32
+ - [`test_capabilities_advertise_mcp`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpCliSafetyTest::test_capabilities_advertise_mcp`
33
+
34
+ ### `AIF-003` - MCP exposes complete Joplin object workflows
35
+
36
+ **Contract:** MCP MUST support validated note, notebook, tag, and resource
37
+ read/write workflows, relationship traversal, search, trash/restore for notes
38
+ and notebooks, and permanent deletion only where Joplin has no trash API.
39
+ Binary content MUST enter and leave as bounded base64 data, never a server-side
40
+ path. Explicit create operations MUST compare their normalized natural
41
+ identity before mutation, including matching trashed objects, and MUST return a
42
+ non-retryable `*_ALREADY_EXISTS` error with every existing ID and the matching
43
+ update tool rather than create or silently reuse a duplicate. Create preflight
44
+ and mutation MUST be serialized within one bridge process.
45
+
46
+ **Evidence:**
47
+
48
+ - [`test_note_crud_metadata_tags_and_search`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_note_crud_metadata_tags_and_search`
49
+ - [`test_notebook_crud_nesting_notes_trash_and_restore`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_notebook_crud_nesting_notes_trash_and_restore`
50
+ - [`test_tag_crud_listing_and_note_relations`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_tag_crud_listing_and_note_relations`
51
+ - [`test_resource_crud_content_and_note_relations`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_resource_crud_content_and_note_relations`
52
+ - [`test_create_note_from_html_and_with_binary_attachments`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_note_from_html_and_with_binary_attachments`
53
+ - [`test_create_tools_reject_existing_object_identities`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_tools_reject_existing_object_identities`
54
+ - [`test_notebook_title_resolves_only_one_root_destination`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_notebook_title_resolves_only_one_root_destination`
55
+ - [`test_concurrent_note_create_allows_exactly_one_object`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_concurrent_note_create_allows_exactly_one_object`
56
+ - [`test_create_rejects_matching_trashed_note_and_notebook`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_create_rejects_matching_trashed_note_and_notebook`
57
+ - [`test_existing_note_create_returns_conflict_with_update_target`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_existing_note_create_returns_conflict_with_update_target`
58
+ - [`test_ambiguous_notebook_title_returns_candidate_ids`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_ambiguous_notebook_title_returns_candidate_ids`
59
+ - [`test_02_explicit_creates_reject_existing_natural_identities`](../../tests_live/test_mcp_live.py) - `tests_live/test_mcp_live.py::LiveMcpTest::test_02_explicit_creates_reject_existing_natural_identities`
60
+ - [`test_02_explicit_creates_reject_existing_natural_identities`](../../tests_live/test_gpt_actions_live.py) - `tests_live/test_gpt_actions_live.py::LiveGptActionsTest::test_02_explicit_creates_reject_existing_natural_identities`
61
+
62
+ ### `AIF-004` - Invalid operations fail before side effects
63
+
64
+ **Contract:** Tool definitions and instances MUST pass the supported JSON
65
+ Schema subset before handler execution. Unknown objects, invalid content,
66
+ unsupported combinations, and Joplin failures MUST return structured stable
67
+ domain errors rather than transport crashes or partial writes.
68
+
69
+ **Evidence:**
70
+
71
+ - [`test_schema_definition_and_instance_validation`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_schema_definition_and_instance_validation`
72
+ - [`test_entity_and_content_validation_errors_are_structured`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_entity_and_content_validation_errors_are_structured`
73
+ - [`test_http_and_schema_validation_precede_side_effects`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_http_and_schema_validation_precede_side_effects`
74
+
75
+ ### `AIF-005` - Joplin outages do not terminate the bridge
76
+
77
+ **Contract:** Binding the agent listener MUST NOT require Joplin availability.
78
+ Each call MAY perform a bounded availability wait and retry reads. An outage
79
+ MUST return a retryable structured tool error and later calls MUST recover.
80
+ Writes MUST be sent once after preflight; ambiguous outcomes MUST be
81
+ non-retryable and exposed for operator inspection.
82
+
83
+ **Evidence:**
84
+
85
+ - [`test_joplin_outage_is_retryable_tool_error_and_server_recovers`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_joplin_outage_is_retryable_tool_error_and_server_recovers`
86
+ - [`test_executor_classifies_expected_failures`](../../tests/unit/test_tool_registry.py) - `tests/unit/test_tool_registry.py::test_executor_classifies_expected_failures`
87
+
88
+ ### `AIF-006` - Actions authentication is independent and fail-closed
89
+
90
+ **Contract:** GPT Actions MUST remain disabled unless explicitly enabled with
91
+ a dedicated protected bearer file. Authentication MUST occur before route
92
+ lookup, files MUST be re-read per request for rotation, malformed or duplicate
93
+ authorization MUST fail closed, and Joplin, MCP, and Actions credentials MUST
94
+ remain independent.
95
+
96
+ **Evidence:**
97
+
98
+ - [`test_authentication_is_uniform_independent_and_rotatable`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_authentication_is_uniform_independent_and_rotatable`
99
+ - [`test_malformed_and_duplicate_authorization_fail_closed`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_malformed_and_duplicate_authorization_fail_closed`
100
+ - [`test_route_is_fail_closed_and_auth_hides_route_details`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_route_is_fail_closed_and_auth_hides_route_details`
101
+ - [`test_mcp_actions_and_joplin_credentials_are_independent`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_mcp_actions_and_joplin_credentials_are_independent`
102
+
103
+ ### `AIF-007` - OpenAPI is generated, deterministic, and secret-free
104
+
105
+ **Contract:** The Actions OpenAPI 3.1 document MUST be generated from the
106
+ exposed registry for one strict production HTTPS origin. It MUST be
107
+ deterministic and contain no credential, note data, local path, upstream Joplin
108
+ URL, MCP route, or health route. Disabled operations MUST not be exported.
109
+
110
+ **Evidence:**
111
+
112
+ - [`test_contract_is_deterministic_and_secret_free`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_contract_is_deterministic_and_secret_free`
113
+ - [`test_production_server_url_is_strict`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_production_server_url_is_strict`
114
+ - [`test_cli_exports_contract_and_advertises_optional_transport`](../../tests/unit/test_gpt_openapi.py) - `tests/unit/test_gpt_openapi.py::test_cli_exports_contract_and_advertises_optional_transport`
115
+
116
+ ### `AIF-008` - Public transports enforce bounded resource use
117
+
118
+ **Contract:** MCP and Actions MUST bound request size, decoded resource size,
119
+ JSON nesting, active handlers, and stalled connections. Actions MUST also
120
+ bound response size, concurrent execution, and authenticated request rate.
121
+ Oversized successful write results MAY be replaced by an explicit omitted
122
+ result but MUST NOT be reported as a failed write.
123
+
124
+ **Evidence:**
125
+
126
+ - [`test_pre_auth_connection_count_is_bounded`](../../tests/integration/test_mcp_server.py) - `tests/integration/test_mcp_server.py::McpHttpTest::test_pre_auth_connection_count_is_bounded`
127
+ - [`test_request_limit_and_result_limit_semantics`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_request_limit_and_result_limit_semantics`
128
+ - [`test_concurrency_capacity_and_authenticated_rate_limit`](../../tests/integration/test_gpt_actions.py) - `tests/integration/test_gpt_actions.py::GptActionsHttpTest::test_concurrency_capacity_and_authenticated_rate_limit`
129
+ - [`test_json_nesting_limit_is_independent_of_parser_recursion_behavior`](../../tests/unit/test_json_safety.py) - `tests/unit/test_json_safety.py::test_json_nesting_limit_is_independent_of_parser_recursion_behavior`