6.7 KiB
JOPLIN Harness SOP
Overview
cli-anything-joplin is a stateful CLI harness for Joplin automation backed by
the real joplin terminal binary. It is intended for command-driven
workflows, real-backend validation, and agent demo runs.
Requirements
- Python 3.10+
- Joplin terminal CLI installed and available in
PATHasjoplin - The harness package installed in editable mode for local development
Verify the backend with:
where joplin # or `which joplin` on POSIX
joplin help
Install
cd joplin/agent-harness
pip install -e .
Usage
# REPL mode (default)
cli-anything-joplin
# One-shot command mode with JSON output
cli-anything-joplin --json notebooks list
# Use a project file
cli-anything-joplin --project ./demo.joplin-harness.json notes create "hello"
# Dry-run (no auto-save)
cli-anything-joplin --json --dry-run --project ./demo.joplin-harness.json notes create temp
Command groups
project:new,open,save,info,json,statusnotebooks:list,create,use,removenotes:list,create,set,get,remove,copy,move,renametodos:list,create,toggle,clear,done,undonetags:list,add,remove,notetags,tagnotessearch:runsync:run(--target,--upgrade,--use-lock)interop:import,exportconfig:get,set,list,export,import-fileattach:addstatus:show,restorebackend:version,dump,keymap,geoloc,export-sync-statusserver:status,start,stope2ee:status,target-status,decrypt,decrypt-filesession:status,undo,redo,history
State model
The harness project is JSON-based and stores:
namecreated_at,updated_at- backend settings such as
binaryandprofile - user context such as
current_notebook historyas an operations log
The session keeps in-memory project state plus undo/redo snapshots.
Save behavior
- One-shot mutating commands auto-save when a project is loaded.
--dry-rundisables auto-save.- REPL mode does not auto-save; the user saves explicitly with
project save. - Commands that should not produce an undo snapshot (e.g.
sync run,interop export) still mark the project dirty viaSession.mark_dirty()so the auto-save still persists their history entry.
JSON output contract
When --json is enabled, commands return a stable envelope:
ok: booleancommand: command identifier such asnotes.list,todos.toggledata: command payloaderror:nullon success, or{ type, message }on failure
Testing strategy
1. Unit / command tests (test_core.py)
Validate harness internals and CLI contract without any backend.
2. CLI subprocess tests (TestCLISubprocess)
Run the installed cli-anything-joplin (or python -m) entry point and
assert the externally visible JSON contract for non-mutating commands.
3. Real-backend command tests (TestBackendCommands)
Single-command checks against the real backend on a fresh profile.
4. Real-backend workflow tests (TestBackendWorkflows)
Short user-flow scripts covering note lifecycle, organization, todos, tagging, search (best-effort), sync, export, import, attach, history.
5. End-to-end integration test (TestBackendIntegration)
Full demo flow used for regression and agent demonstrations; verifies that the saved project history captures every action performed.
Test commands
python -m pytest -q cli_anything/joplin/tests/test_core.py
python -m pytest -q cli_anything/joplin/tests/test_full_e2e.py::TestCLISubprocess
python -m pytest -v cli_anything/joplin/tests/test_full_e2e.py::TestBackendCommands
python -m pytest -v cli_anything/joplin/tests/test_full_e2e.py::TestBackendWorkflows
python -m pytest -v cli_anything/joplin/tests/test_full_e2e.py::TestBackendIntegration
python -m pytest -v --tb=no cli_anything/joplin/tests
For real backend runs, ensure joplin is installed and available in PATH.
Current validation baseline (Windows + Joplin CLI 3.6.2):
python -m pytest -q cli_anything/joplin/tests/test_core.py->107 passedpython -m pytest -q cli_anything/joplin/tests->134 passed, 1 skipped
Development notes
- Prefer adding new coverage as a small command test first.
- Promote longer user journeys into workflow tests.
- Keep exactly one full integration flow for demonstration and regression.
- Preserve the JSON envelope and backend command naming conventions.
- Prefer Joplin's native
--format jsonfor list-style commands when the source command supports it (lsdoes;tag listdoes not). - New mutating commands should either call
sess.snapshot(reason)(creates an undo point) orsess.mark_dirty()(just marks the project as modified) so auto-save still persists the appended history entry. utils/joplin_backend.pyreturns rawstdout/stderrfrom Joplin. The benign-Node-warning filter is used only inside the non-zero exit decision (and once insiderun_joplin_jsonwhen JSON parsing fails on stdout that may contain a warning prefix). Never rewrite caller-facing stdout.- Error envelopes must use the same
commandstring as success payloads.handle_errorderives IDs withfunc.__name__.replace("_", ".", 1)so multi-word subcommands stayconfig.import_file, notconfig.import.file.
Known limitations
- Some Joplin CLI builds reject
searchoutside the REPL with"only available in GUI mode". The harness surfaces this as a normalok=falseJSON envelope; agents should treat search as best-effort. - Non-ASCII process arguments on Windows pass through
joplin.cmd?cmd.exe, which truncates them to the active code page. Harness JSON state handles unicode correctly; only argv-forwarded titles are affected. The unicode workflow test is skipped on Windows. notes remove --permanent/notebooks remove --permanentrequire Joplin terminal CLI >= 3.0. Older builds silently ignore unknown options, so the harness probesjoplin help rmnote/rmbookonce per binary and raises a clearRuntimeErrorrather than letting a "permanent" delete fall through to a soft trash move. The flag is forwarded as the long-form--permanent(and--forcefor force), never the short-p/-f.server start --exit-early/--quietande2ee decrypt --forceare gated by the samejoplin help <command>probe (see_cli_supports_flagincore/backend.py). They are real options of Joplin 3.x, but the same "silently ignored unknown option" behaviour means dropping them on older builds would either hang the harness on the server foreground loop or deadlock e2ee on an interactive master-password prompt. The probe raises a clear, actionable error in those cases.