Agent-friendly CLI and SDK for local Joplin desktop
Project description
joplin-cli
Agent-friendly CLI and Python SDK for controlling a running local Joplin desktop instance through the Joplin Clipper REST API.
joplin-cli is designed for humans and coding agents that need predictable,
single-shot commands. It does not provide a REPL or TUI. Every command starts,
does one thing, prints useful output or a structured error, and exits.
What It Does
- Connects to the local Joplin desktop Web Clipper service, usually at
http://127.0.0.1:41184. - Auto-discovers the local desktop profile token from Joplin's
settings.jsonwhen Joplin is already configured on the machine. - Exposes note, notebook, search, tag, todo, resource, diagnostic, config, and batch operations.
- Works as both an installable CLI and an importable Python SDK.
- Keeps token values out of normal output, diagnostics, error messages, and object representations.
- Returns agent-recoverable errors with
Error,Cause,Try, andExamplessections where applicable.
Installation
Install the published CLI from PyPI:
uv tool install joplin-cli
Upgrade an existing installation:
uv tool upgrade joplin-cli
Run directly from a checkout during development:
uv run joplin-cli --help
The package installs the joplin-cli command. It does not install a joplin
command by default, because that name may already belong to another Joplin
tool. Check the optional alias workflow with:
joplin-cli alias status
Quick Start
uv tool install joplin-cli
joplin-cli doctor
joplin-cli notebooks list
joplin-cli notes list limit=10
joplin-cli search query="meeting notes" --json
doctor is the best first command. It checks whether the local Joplin server is
reachable, whether a token can be found, and what to run next.
Agent Usage
Every command is single-shot. Use --json for machine-readable output.
joplin-cli notes read id=<note-id> --json
joplin-cli notes create title="Draft" body=@./draft.md
joplin-cli notes append id=<note-id> content="- [ ] Follow up"
joplin-cli batch delete query="tag:temporary" dry-run
For long Markdown content, write it to a UTF-8 file and pass the file as a text
parameter. body=@./draft.md reads the note body from disk, and
content=@./section.md does the same for append/prepend content. Use
body=@@literal when the literal value must start with @.
Design rules that matter for agents:
- Use
key=valuearguments for predictable shell generation. - Prefer
--jsonwhen another tool will parse the result. - Use
@filefor longbodyorcontentvalues instead of putting large Markdown blocks directly in the shell command. - Use
joplin-cli helporjoplin-cli <group> --helpto discover commands. - Errors explain the likely cause and a concrete next command.
- Validation errors exit with code
2; connection, auth, not-found, and conflict errors use distinct exit codes.
Authentication
Default behavior is intentionally low-friction for local use. If Joplin desktop
is already running and the Web Clipper service is enabled, joplin-cli tries to:
- Connect to
127.0.0.1:41184. - Find the Joplin desktop profile.
- Read
api.tokenfrom the profilesettings.json. - Use the token without printing it.
Override discovery when needed:
$env:JOPLIN_TOKEN="..."; joplin-cli notes list
joplin-cli config set token=...
joplin-cli config set port=41184
joplin-cli config path
Supported environment variables:
JOPLIN_TOKENJOPLIN_HOSTJOPLIN_PORTJOPLIN_PROFILEJOPLIN_TIMEOUTJOPLIN_CLI_CONFIG
Token precedence is: CLI option, environment variable, joplin-cli config,
auto-discovered Joplin profile.
Common Commands
Notes:
joplin-cli notes list limit=20
joplin-cli notes read id=<note-id>
joplin-cli notes create title="Draft" body="# Draft"
joplin-cli notes create title="Draft" body=@./draft.md
joplin-cli notes update id=<note-id> title="New title"
joplin-cli notes update id=<note-id> body=@./draft.md
joplin-cli notes append id=<note-id> content="- [ ] Follow up"
joplin-cli notes append id=<note-id> content=@./section.md
joplin-cli notes delete id=<note-id>
Notebooks:
joplin-cli notebooks list
joplin-cli notebooks tree
joplin-cli notebooks create title="Projects"
joplin-cli notebooks rename id=<notebook-id> title="Archive"
Search, tags, and todos:
joplin-cli search query="meeting notes" --json
joplin-cli tags list
joplin-cli tags add note=<note-id> tag=<tag-id>
joplin-cli todos list open
joplin-cli todos done id=<todo-id>
Resources:
joplin-cli resources list
joplin-cli resources attach note=<note-id> path="./file.pdf"
joplin-cli resources download id=<resource-id> output="./file.pdf"
Output Formats
Text output is compact by default. Use JSON for automation:
joplin-cli notes list limit=10 --json
Other tabular formats are available where the output is list-like:
joplin-cli notes list limit=10 --format tsv
joplin-cli notes list limit=10 --format csv
Batch Delete Safety
Batch delete is intentionally two-step. First run a dry run:
joplin-cli batch delete query="tag:temporary" dry-run
The dry run prints:
- The number of matching notes.
- A preview containing note IDs and titles.
- A confirmation token shaped like
delete-2-notes-<hash>.
Only then run the destructive command:
joplin-cli batch delete query="tag:temporary" confirm=delete-2-notes-<hash>
The confirmation token is bound to the query and matched note IDs, not just the count. A token from one query cannot confirm another query that happens to match the same number of notes.
For automation that has already done its own safety checks, yes bypasses the
confirmation token:
joplin-cli batch delete query="tag:temporary" yes
Python SDK
The SDK is the core layer; the CLI is a thin wrapper around it.
from joplin_cli import JoplinClient
with JoplinClient.auto() as client:
notes = client.notes.list(limit=10)
first = notes[0]
print(first.id, first.title)
Explicit connection:
from joplin_cli import JoplinClient
client = JoplinClient(host="127.0.0.1", port=41184, token="...")
try:
notebooks = client.notebooks.list()
finally:
client.close()
Main SDK services:
client.notebooksclient.notesclient.searchclient.tagsclient.todosclient.resourcesclient.batch
Error Model
CLI errors are intended to be actionable without reading source code:
Error: Parameter limit must be an integer.
Try: Use limit=5.
Exit codes:
0: success1: general API/output error2: validation or CLI usage error3: local Joplin connection error4: authentication error5: not found6: conflict or destructive action not confirmed
Development
Install the current checkout as a tool while testing packaging:
uv tool install . --force
Install dependencies and run checks:
uv sync
uv run pytest -v
uv run ruff check .
uv run ty check
Optional live smoke test against a running local Joplin desktop:
$env:JOPLIN_CLI_LIVE="1"; uv run pytest tests/live/test_live_joplin.py -v
The live test only reads notebooks. It does not create, edit, or delete Joplin data.
Release
PyPI publishing is configured through GitHub Actions trusted publishing. Create
and publish a GitHub Release from this repository; the release.yml workflow
will run tests, linting, type checks, build the distributions, and publish them
to PyPI without a stored PyPI token.
Before creating a release, update version in pyproject.toml and verify the
package locally:
uv run pytest -q
uv run ruff check .
uv run ty check
uv build
Troubleshooting
If doctor says the server is offline:
joplin-cli doctor
Check that Joplin desktop is running and that the Web Clipper service is enabled.
If token discovery fails, inspect configuration:
joplin-cli auth
joplin-cli config path
joplin-cli config get token
Token values are redacted in command output.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file joplin_cli-0.1.2.tar.gz.
File metadata
- Download URL: joplin_cli-0.1.2.tar.gz
- Upload date:
- Size: 36.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a38ab394a57735b87cbaf42dbc5d3852683abf1e6bb977e38a4ca6e3416c3384
|
|
| MD5 |
2700e665aafc7cb4cd81642137703565
|
|
| BLAKE2b-256 |
37f4aa92b04040c5d4bf83a644a5aa49ca16577d3c4597654a83caf7d201a29c
|
File details
Details for the file joplin_cli-0.1.2-py3-none-any.whl.
File metadata
- Download URL: joplin_cli-0.1.2-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c22ea706d61d7b187fbe0f55b7434675fd18b46ce262ebb5750cdf0ae278ccc0
|
|
| MD5 |
b3ec47b262de3b565728a47667e47870
|
|
| BLAKE2b-256 |
a71f9ae6f89556a2f6d1381d0c56209ed83e4d1532d22a41670958d2b3de3840
|