dither
CLI reference

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; no PATH install required. Set DITHER_USE_SYSTEM_DENO=1 to use the system deno instead.

When to use install vs run

  • install configures: 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 mutate grants/<name>.json.
  • run <path> is "install + run" — if the target is a directory containing a package.json with a dither block, 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.

FlagGrants
--env KEY=VALUE,...Literal env values written into input.env for the plugin to read.
--allow-env KEY,KEY2Read 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,HOST2Net 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 for KEY=VALUE pairs. 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 / flagDescription
sourcePositional. Path to the plugin directory (must contain package.json). Required.
--envLiteral env values. See above.
--allow-envGlobal env names this plugin may read.
--fileFile/folder paths to grant.
--allow-netHosts the plugin may reach.
--allow-collectionCollections the plugin may promote into.

run

dither plugin run <target> [flags]

Executes a plugin once. Trigger is recorded as manual. The runner:

  1. Resolves grants from <dither-home>/grants/<name>.json, layering flag values as per-run overrides.
  2. Resolves env: per-run override > grant literal > grant envRefs looked up in <dither-home>/env.json > manifest default > error.
  3. Creates a fresh run dir at <dither-home>/runs/<runId>/.
  4. Writes input.json ({ trigger, env, files, targets }) for the plugin to read.
  5. Spawns deno with --allow-read, --allow-write, --allow-env=DITHER_*, and (if granted) --allow-net.
  6. Promotes every *.md file in the run dir to <library>/<collection>/, after verifying frontmatter source matches the plugin name and collection is in grants.collections.
  7. Refreshes the qmd index for the touched collections only.
  8. 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 / flagDescription
targetPositional. Installed plugin name OR path to a plugin directory. Required.
--detachFork 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, --verboseForward plugin stderr (Deno output, console.log/error) to your terminal in real time. Default off.
--no-auto-openmacOS only. Suppress the Open System Settings now? [Y/n] prompt that appears on a recognised Full Disk Access failure.
--envLiteral env values (override).
--allow-envAdditional global env names this run may read.
--fileFile path overrides (added to --allow-read for this run).
--allow-netAdditional hosts for this run.
--allow-collectionAdditional 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.log

Run-time errors carry specific exit codes:

  • Plugin not installed → exit 1 with error: plugin not installed: '<name>' plus a hint to run dither 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-open to suppress.

list

dither plugin list

Tab-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    daily

The 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.

ArgumentDescription
namePositional. Name of an installed plugin. Required.
$ dither plugin remove import-folder
removed import-folder

Errors if the plugin is not installed.

See also: CLI overview, dither env, Security, Plugin authoring.