# Exbyte Depot — Plugins Plugins add features on top of the core app so the base stays lean and specialised work ships (and updates) independently. ## What language? **JavaScript / TypeScript.** A plugin is a plain **ES module** that exports `activate(host)`. Author in TypeScript if you like, then compile to a single JS file (`esbuild index.ts --bundle --format=esm --outfile=index.js`). No build step is required for plain JS. Why JS and not native? The app's Rust backend is compiled and sealed; the extensible surface is the webview. Plugins run in the app's webview and reach the app **only** through the `host` object — never `invoke` directly. ## How plugins are loaded The app reads the plugin's entry source from disk (Rust) and imports it as an ES module from a `blob:` URL — **no `eval`**, and it stays inside the CSP (`script-src 'self' blob:`). Disabled plugins are never imported. ## Anatomy ``` my-plugin/ plugin.json # manifest index.js # ES module exporting activate(host) ``` ### plugin.json ```json { "id": "my-plugin", // unique; [A-Za-z0-9-_.] "name": "My Plugin", "version": "1.0.0", "author": "You", "description": "What it does.", "entry": "index.js", // must stay inside the plugin folder "minAppVersion": "0.3.5", "permissions": ["p4:read", "notify"], "contributes": { "menu": true, "views": ["notes"], "fileContext": true, "submitHooks": true } } ``` ### Permissions (declared, shown to the user) | permission | grants | |---|---| | `p4:read` | read-only p4: `opened, dirs, describe, changeSizes, clientSpec` on `host.p4` | | `p4:write` | mutating p4: `add, edit, del, sync, reconcile` on `host.p4` | | `notify` | native notifications via `host.notify` | Without a permission, the matching APIs are simply absent (or no-ops). `host.storage`, `host.clipboard`, `host.flash`, `host.info`, `host.t`, `host.theme` are always available. ## The `host` SDK ```ts host.version // Host SDK version, e.g. "1.0" host.plugin // { id, name, dir } host.has(perm) // boolean host.p4.(...) // Promise — only the whitelisted set your perms allow host.info() // { userName, clientName, clientRoot } | null host.selectedFile() // { depotFile } | null host.refresh() // re-read the Changes view host.theme() // current theme's CSS-var overrides host.ui.addMenuItem(label, run) // Actions-menu action host.ui.addFileContextItem(label, run(file)) // right-click a file host.ui.addView({ id, title, render(el, host) })// a panel; render into a DOM element (return an optional cleanup fn) host.ui.addSubmitHook(run(files) => {ok, message}) // block/allow a commit host.flash(text, isError?) // toast host.notify(title, body) // native notification (needs "notify") host.clipboard.write(text) // Promise host.storage.get/set/remove(key) // per-plugin localStorage namespace host.t(key, params?) // app i18n host.log(...args) // console, tagged with the plugin id ``` ## Theme tokens A plugin's DOM inherits the app's CSS variables — use them so the panel follows the active theme. The exact names (a wrong name silently falls back to your hardcoded default, which then breaks on the opposite theme): `--accent` `--accent-2` `--accent-deep` · `--bg` `--panel` `--panel-2` `--panel-3` `--elevated` `--border` · `--txt` `--muted` `--faint` · `--add` (success) `--edit` (warning) `--del` (danger) · `--mono` (font) There is no `--text`, `--ok`, `--err`, or `--sans`. ## Contribution points - **Menu items** and **views** appear in the **Actions** menu. - **File context items** appear when you right-click a file in Changes. - **Submit hooks** run before a commit is finalised; returning `{ ok:false, message }` prompts the user to confirm or cancel. (This is exactly where a reference-integrity guard, size check, or lint would live.) ## Import methods 1. **Install from folder** — Actions → Plugins… → *Install from folder…* → pick a folder that contains `plugin.json`. It's copied into the app's plugins folder. 2. **Drop-in** — Actions → Plugins… → *Open plugins folder*, drop `my-plugin/` in there, then *Reload*. 3. **(Planned) Registry** — a signed registry (`exbyte-depot-plugins` repo, minisign-signed bundles) for one-click install/updates, reusing the app's updater signing infra. ## Try the example `plugins-examples/hello-exbyte/` demonstrates all four contribution points plus storage, clipboard, info and notifications. Install it from folder, then: - Actions → **Say hello 👋** and Actions → **Workspace Notes** - right-click a file → **Copy depot path (plugin)** - try to commit a file named `wip*` → the submit guard warns. ## Safety Plugins run in-app on the main thread — install only ones you trust. The `id` is path-validated, the entry cannot escape the plugin folder, and each plugin's storage is namespaced. A plugin that throws is isolated and does not crash the app.