dither plugin
Install, run, list, and remove plugins.
Plugins are small Deno programs that write markdown into collections you've granted them. The CLI manages four lifecycle actions: install, run, list, and remove. install and run refuse with the standard pre-init error until dither init has run; list and remove work without init (a fresh install has nothing to list or remove anyway).
Plugins run under Deno. dither downloads and pins its own Deno at
<dither-home>/bin/deno-<version>on first need; noPATHinstall required. SetDITHER_USE_SYSTEM_DENO=1to use the systemdenoinstead.
When to use install vs run
installconfigures: it copies the plugin into<dither-home>/plugins/<name>/, validates its manifest, and writes a persistent grants record to<dither-home>/grants/<name>.json. The flags you pass become the grants.run <name>executes an installed plugin once. Grant flags here are ephemeral overrides — they layer on top of grants for this run only and don't mutategrants/<name>.json.run <path>is "install + run" — if the target is a directory containing apackage.jsonwith aditherblock, the flags persist as grants and the plugin runs immediately. Re-running by path with new flags is the idiomatic way to update grants in-place.
The grant flags
These are identical on install and run. They map one-to-one to the grant kinds.
| Flag | Grants |
|---|---|
--env KEY=VALUE,... | Literal env values written into input.env for the plugin to read. |
--allow-env KEY,KEY2 | Read access to those names in the global env store (<dither-home>/env.json). |
--file ID=PATH,... | A file/folder path. Implies --allow-read=<path> for the Deno sandbox. |
--allow-net HOST,HOST2 | Net access to those hosts. Manifest net is the install-time default when this flag is omitted. |
--allow-collection NAME,... | Promote into those collections (glob patterns supported, e.g. messages/**). Manifest collections is the install-time default when this flag is omitted. |
If a flag is omitted, the corresponding grant defaults to everything the manifest declared for that field. When you supply a flag, that's the grant — the manifest is not a ceiling. The grants file is the source of truth at promote time. See Security: default-grant from manifest.
Comma is the only separator and
=is reserved forKEY=VALUEpairs. Values containing,or=aren't expressible through the CLI flag — use the programmatic API.
install
dither plugin install <source> [flags]Copies the plugin from a local directory into <dither-home>/plugins/<name>/, validates its manifest, resolves grants, and writes <dither-home>/grants/<name>.json. Required env (no default, not granted via --allow-env) must be supplied or install fails before anything is written to disk.
dither plugin install ./my-plugin \
--env "MAX_RETRIES=3,GREETING=hi" \
--allow-env OPENAI_API_KEY \
--file "SOURCE=/abs/path/to/file" \
--allow-net api.openai.com \
--allow-collection notes
# installed my-plugin@0.1.0
# → /Users/me/.dither/plugins/my-plugin| Argument / flag | Description |
|---|---|
source | Positional. Path to the plugin directory (must contain package.json). Required. |
--env | Literal env values. See above. |
--allow-env | Global env names this plugin may read. |
--file | File/folder paths to grant. |
--allow-net | Hosts the plugin may reach. |
--allow-collection | Collections the plugin may promote into. |
run
dither plugin run <target> [flags]Executes a plugin once. Trigger is recorded as manual. The runner:
- Resolves grants from
<dither-home>/grants/<name>.json, layering flag values as per-run overrides. - Resolves env: per-run override > grant literal > grant
envRefslooked up in<dither-home>/env.json> manifest default > error. - Creates a fresh run dir at
<dither-home>/runs/<runId>/. - Writes
input.json({ trigger, env, files, targets }) for the plugin to read. - Spawns
denowith--allow-read,--allow-write,--allow-env=DITHER_*, and (if granted)--allow-net. - Promotes every
*.mdfile in the run dir to<library>/<collection>/, after verifying frontmattersourcematches the plugin name andcollectionis ingrants.collections. - Refreshes the qmd index for the touched collections only.
- Deletes the run dir.
<target> is either an installed plugin name (flags are ephemeral overrides) or a path to a plugin directory (auto-installs/reinstalls; flags persist as grants):
# By name — flags layer ephemerally
dither plugin run my-plugin --env "MAX_RETRIES=10"
# By path — installs (or reinstalls) with the supplied flags as persisted grants
dither plugin run ./my-plugin --env "FOO=bar"| Argument / flag | Description |
|---|---|
target | Positional. Installed plugin name OR path to a plugin directory. Required. |
--detach | Fork the run into the background and return immediately. Stdout/stderr go to <dither-home>/logs/<name>-<ms>.log (where <ms> is the spawn epoch in milliseconds). |
-v, --verbose | Forward plugin stderr (Deno output, console.log/error) to your terminal in real time. Default off. |
--no-auto-open | macOS only. Suppress the Open System Settings now? [Y/n] prompt that appears on a recognised Full Disk Access failure. |
--env | Literal env values (override). |
--allow-env | Additional global env names this run may read. |
--file | File path overrides (added to --allow-read for this run). |
--allow-net | Additional hosts for this run. |
--allow-collection | Additional collections for this run. |
By default, run blocks until the plugin exits. While it runs, the host overwrites a single status line with the latest progress({ message }) the plugin emitted. On a non-TTY each message lands on its own line.
$ dither plugin run import-folder
run 20260507T084600-import-folder-a1b2 promoted 3 entries:
/Users/me/Documents/dither/imported/note-1.md
/Users/me/Documents/dither/imported/note-2.md
/Users/me/Documents/dither/imported/note-3.md
$ dither plugin run imessage --detach
detached run for imessage (pid 41210)
logs: /Users/me/.dither/logs/imessage-1746543210123.logRun-time errors carry specific exit codes:
- Plugin not installed → exit 1 with
error: plugin not installed: '<name>'plus a hint to rundither plugin list. - macOS Full Disk Access required (detected from the plugin's stderr) → exit with the plugin's exit code, and on a TTY an interactive
Open System Settings now? [Y/n]prompt that opens the FDA pane. Use--no-auto-opento suppress.
list
dither plugin listTab-separated: <name>\t<version>\t<collections>\t<schedule>. Prints (no plugins installed) when <dither-home>/grants/ is empty or missing.
$ dither plugin list
import-folder 0.0.1 imported -
arxiv-watch 0.2.0 papers dailyThe schedule column shows the manifest's declared schedule. If a plugin has schedule or watch.collections, dither plugin install (and run <path>) will lazily start the daemon so it picks up the cron / file-watch trigger; you can also invoke runs manually at any time. See dither daemon start for the long-running side.
remove
dither plugin remove <name>Deletes <dither-home>/plugins/<name>/ and <dither-home>/grants/<name>.json. Plugin state under the plugin dir goes with it (state preservation across reinstall is a later concern). Already-promoted entries under the library are untouched.
| Argument | Description |
|---|---|
name | Positional. Name of an installed plugin. Required. |
$ dither plugin remove import-folder
removed import-folderErrors if the plugin is not installed.
See also: CLI overview, dither env, Security, Plugin authoring.