Plugin System
Understand installed and project-level Daintree plugins: installation, project trust, management, updates, and building your own repository tools.
Plugins extend Daintree with panels, commands, toolbar buttons, settings, recipes, skills, agents, and integrations. Installed plugins live in ~/.daintree/plugins/ and operate across projects. Project-level plugins live in the repository at .daintree/plugins/ and supply tools for that project alone.
A project plugin can turn a repository into a small application for its team: a release desk, content studio, data workbench, or operations console. Start with Project-level Plugins for use cases and the available surfaces, Building Project Plugins for the development workflow, Project Plugin Surfaces for a custom starting screen, and Project Plugin Trust & Management for running repository code.
Installing a plugin
A packaged plugin is a .dntr file: a standard ZIP archive with plugin.json at its root. There is no marketplace and no central registry; authors publish .dntr files on GitHub Releases, their own site, or an internal artifact host. Packaged Daintree distributions associate the .dntr extension with the app, so opening a downloaded archive can start the install flow.
| Path | How |
|---|---|
| Drag and drop | Drop one or more .dntr files anywhere on the Plugin Manager's body. A multi-file drop installs sequentially, one confirm per file. |
| Install from file | Install from file… in the Plugin Manager, or double-click a .dntr in your file manager. |
| Install from URL | Install from URL… in the Plugin Manager. Paste a link to a .dntr. |
| Sideload | Put an unpacked plugin directory at ~/.daintree/plugins/{publisher}.{name}/ and restart. This is the path for plugins you build yourself or share inside a team via a private repo. |
| Deep link | daintree://plugin/install?url=… opens Daintree and pre-fills the URL install dialog. daintree://plugin/open?id=publisher.name opens the manager scrolled to that plugin. |
| CLI | daintree-plugin install <path-or-url> installs into the running app. daintree-plugin uninstall <pluginId> removes it. |
Archive installs validate and hash the package, check host compatibility, extract it into a temporary directory, and atomically replace the installed directory before loading. OS-opened and dropped archives show a manifest preview with identity, capabilities, and contributed recipe names before installation. Explicit file-picker, URL, and CLI routes have their own entry flows; do not assume every route shows the same preview. Sideloaded directories and project plugins are discovered directly rather than passing through archive extraction.
Installing a validated archive over an existing plugin does not compare version order: there is no semver comparison, no downgrade gate, and no identical-version block. The swap keeps the original installedAt and records an updatedAt.
URL installs have a 30 MiB cap, a 30-second deadline, archive content-type checks, and at most five redirects. The shared download guard requires HTTPS and rejects embedded credentials, private, loopback, and link-local targets. The UI still displays a warning for HTTP, but confirming it does not bypass the HTTPS guard.
.dntr archives are not signed, and Daintree performs no publisher-identity check at any install path. The SHA-256 hash proves that two fetches of the same URL returned the same bytes; it proves nothing about who produced them. Install only from sources you would trust with your shell.The Plugin Manager
The Plugin Manager is a full-screen view for browsing installed plugins, inspecting their settings and permissions, and managing lifecycle actions. Escape closes it.
Open it from Plugin Manager… in the app menu, from the command palette (Open Plugin Manager), from the plugin toolbar tray, or from Settings > Plugins. There is no default keyboard shortcut; bind one yourself from Keyboard Shortcuts if you open it often.
The layout is master/detail. The left column lists everything installed, grouped by catalog category: Forge providers, AI & agents, Workspace, and Other. A plugin's category comes from its manifest category field when it declares one, and is otherwise derived from what it contributes. A disabled plugin stays in its category, dimmed in place with a badge: state is shown where the row already is, so toggling a plugin never teleports it out from under your cursor.
Each row carries the plugin's icon, name, version, and tagline, plus whichever badges apply: Blocked, Disabled, the install source (file, URL, sideload) when it differs from the default, Dev for a plugin linked by the development loop, Restart required, and Failed when activation threw. The enable switch sits at the right of the row as its own control.
Search and filter operators
The search field does fuzzy matching on name and description, and also accepts VS Code-style @ operators that narrow the list before scoring. A row of chips below the field advertises the category filters and @disabled so the syntax is discoverable rather than hidden.
| Operator | Matches |
|---|---|
@builtin | Plugins that ship with Daintree. |
@installed | Plugins you installed yourself. |
@enabled / @disabled | Current enable state. |
@cat:<id> | Category: forge, ai, workspace, other. |
@cap:<id> | Plugins declaring a capability, e.g. @cap:shell:exec. |
An unrecognized @foo token is left in the free text rather than silently dropped, so a half-remembered operator still gets you a name match instead of an empty list.
The detail pane
Selecting a plugin fills the right pane with its identity, version, provenance, lifecycle actions, and a tab bar. Tabs are earned by content. Overview is always present; Settings appears only when the plugin declares a settings schema, Permissions only when it declares capabilities, and MCP servers only when it contributes one. A minimal plugin gets one tab, not three with two of them empty.
With nothing selected, the pane shows the catalog home: a card grid of the categories with a one-line blurb each. A footer slot below the installed list is reserved for the online browse catalog, which ships separately and currently reads "Online plugin catalog coming soon."
While an install is running, a progress banner reports the phase it is in (Downloading the plugin, Unpacking the plugin, Checking the plugin, Finishing the install), names the archive entry being written, and offers a cancel. If a change cannot be reconciled in the running session, a bar across the header offers a restart behind a confirm dialog. That bar clears itself if the condition resolves while it is open.
Settings > Plugins still exists, but it is now a launcher: the installed count, a button that opens this view, and an opt-in toggle for background update checks.
Managing installed plugins
Enable and disable, without a restart
The switch on each row is live. Disabling unloads the plugin in place: disposers run, its contributions leave the palette and toolbar, and any MCP subprocess it started is killed. Enabling re-forks the worker and loads it again. The Restart required badge reads true only in the brief window where the persisted desired state and the running state have not yet reconciled; it is a transient cue, not the normal path.
Settings
A plugin that declares a settings schema gets a generated form. Settings can be user-scoped, project-scoped in the repository, or local to this machine and project. Local scope is useful for an interpreter path or per-project credential that should not travel with Git. A secret setting uses OS-backed encryption where available, with the storage tier shown in the form and a plaintext fallback when no usable backend exists.
Updates
Nothing auto-updates. Right-click a plugin and choose Check for update: Daintree re-fetches the URL it was installed from and compares the SHA-256 of the archive against the installed one, so a new build is detected by content change regardless of what its version string says. Update all checks every URL-installed plugin and then drains the ones with a change through the same per-plugin confirm: it never installs anything silently. Sideloaded and file-installed plugins have no origin to re-fetch, so they are updated by reinstalling.
Uninstall
Uninstall runs a full cascade: unload the plugin and run its disposers, terminate any MCP subprocesses it spawned, revoke every stored consent pin so a later reinstall re-prompts rather than inheriting prior approvals, and delete its directory. User-scope settings are kept by default so an API token survives a reinstall; tick also remove stored settings (or pass --delete-settings to the CLI) to delete them too. Project-scope settings are never touched: they are tracked per repository and removing them is the project's business.
The blocklist
Daintree fetches a small remote blocklist at startup and refuses to load anything it names. The list is fetched once with an 8 s timeout, cached on disk for offline enforcement, and re-fetched when the cache passes six hours. A fetch failure does not invent new blocks; any cached blocklist remains enforced. Without a usable cached list, the fetch fails open. A blocked plugin still appears in the manager, dimmed, with a Blocked badge, a stated reason, and its switch disabled: the refusal is host policy, not something you toggled.
Project visibility
Project settings → Plugins controls which installed-plugin UI appears in that project. Show here hides or reveals panels, commands, toolbar buttons, keybindings, and context menus; it leaves the global worker and app-wide contributions running. For a repository plugin, Run here actually unloads that project instance. Folder trust is a separate control. See Project Plugin Trust & Management.
Where plugin contributions appear
Contributions are read from the manifest eagerly at startup, before any plugin code runs, so a plugin's commands and panels are in the palette even when the plugin itself has not activated yet. Plugins are lazy by default: the entry module is imported the first time one of its contributions is actually used.
- Commands appear in the command palette under the plugin's own category, and can be bound to keys like any built-in action. See Palettes and Menus.
- Panels appear in the new-panel palette and open into the grid alongside terminals and viewers. See Terminals and Panels.
- Toolbar buttons collect into a single grouped plugin tray button rather than each claiming a top-level slot. You promote the ones you use to their own slot by hovering the tray row and clicking the pin, from Settings > Toolbar. Placement is your call, not the manifest's.
- Menu and context-menu items are appended below Daintree's own entries on the surfaces they target.
- Agents, MCP servers, and skills join the same rosters the built-ins use. See Agents and MCP Server.
What a plugin can and cannot do
The main entries of user-installed plugins run out of process, in a forked utility process with their own module realm and OS-level crash isolation. Host operations and registrations cross a MessagePort and return promises. React views run in Daintree's renderer. Built-in plugins activate their main entries in-process.
The manifest's capabilities array discloses intent and drives host-side policy. High-risk capabilities and compound combinations can elevate an action to confirmation; a command can narrow that derivation with requires. Host-mediated filesystem and Git calls enforce declared roots, and plugin-hosted MCP tools are subject to capability-derived danger limits. Network and socket allowlists describe intent; they do not constrain raw Node calls.
The normal worker runtime is not a security sandbox. A plugin's entry module can call Node directly. React views execute in the app renderer, so the worker's crash isolation does not cover every part of a plugin. Archives are unsigned and publisher identity is not verified. Read Trust & Capabilities for the full boundaries.
Writing a plugin
The shared authoring references cover both plugin origins; individual contribution points state where project scope differs:
- Plugin Manifest: every field in
plugin.json: identity and the plugin id format, version compatibility, capabilities and scopes, activation events, and how strict validation reports mistakes. - Contribution Points: the contribution types, including recipes, project-only surfaces, and the scope restrictions on each.
- Host API: the
activate(host)entry point, the runtime surface it hands you, and the view component contract. - Building & Distributing: the
daintree-pluginCLI, the hot-reload development loop,.dntrpackaging, and how to ship it.
Built-in plugins
Daintree ships its GitHub integration as a built-in plugin rather than as app code. It registers a forge provider (issues, pull requests, reviews, CI roll-up, releases, and auth) and a file-decoration provider that badges review state on changed-file rows in the Review Hub. It is the reference implementation for what a real provider looks like.
Built-in plugins can be disabled but not uninstalled. The switch works exactly as it does for anything else, and disabling GitHub takes its forge provider and decorations out of the app; there is simply no directory to delete, because they live inside the application bundle. They also carry no source badge, since built-in is the default.
The daintree.* namespace is reserved. A user-installed plugin whose name starts with daintree. is rejected at manifest validation.
Source references: plugin loading, archive installation and updates, and project controls, reviewed at the September 5, 2026 revision.