@termix-ssh/plugin-sdk 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/PLUGIN-GUIDE.md +151 -36
- package/cli/commands/build.mjs +36 -22
- package/cli/commands/pack.mjs +12 -2
- package/cli/commands/validate.mjs +63 -3
- package/cli/lib/plugin-dir.mjs +11 -0
- package/cli/lib/tmxplug.mjs +9 -0
- package/dist/backend.d.ts +37 -3
- package/dist/capabilities.js +2 -0
- package/dist/frontend.d.ts +152 -0
- package/dist/frontend.js +7 -0
- package/dist/host/PluginsTab-EV4MSGY6.js +2933 -0
- package/dist/host/{chunk-G346FUE4.js → chunk-AWR6FYDO.js} +8877 -11159
- package/dist/host/{chunk-NZG6HNQH.js → chunk-RUPLD6RF.js} +1504 -15
- package/dist/host/{chunk-DIOJ3KW2.js → chunk-VEWOYAMZ.js} +6 -1
- package/dist/host/chunk-X4QZKO5G.js +3120 -0
- package/dist/host/chunk-XKA3DBB2.js +10375 -0
- package/dist/host/chunk-YGYBM7SQ.js +58 -0
- package/dist/host/core-classes.json +1 -1
- package/dist/host/{linked-server-QAKKDBFM.js → linked-server-B4NEG4MA.js} +2 -2
- package/dist/host/{rbac-api-5267EMX7.js → rbac-api-OUO3LUOC.js} +2 -2
- package/dist/host/testing-host.js +123 -57
- package/dist/host/types/src/types/ui-types.d.ts +2 -1
- package/dist/host/types/src/ui/api/audit-log-api.d.ts +46 -0
- package/dist/host/types/src/ui/api/host-defaults-api.d.ts +42 -0
- package/dist/host/types/src/ui/api/host-tags-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/look-api.d.ts +40 -0
- package/dist/host/types/src/ui/api/open-tabs-api.d.ts +4 -0
- package/dist/host/types/src/ui/api/plugins-api.d.ts +26 -0
- package/dist/host/types/src/ui/api/private-endpoints-api.d.ts +6 -0
- package/dist/host/types/src/ui/api/tls-api.d.ts +25 -0
- package/dist/host/types/src/ui/auth/ElectronLoginForm.d.ts +9 -0
- package/dist/host/types/src/ui/components/command.d.ts +78 -0
- package/dist/host/types/src/ui/components/folder-style.d.ts +16 -0
- package/dist/host/types/src/ui/components/theme-provider.d.ts +2 -1
- package/dist/host/types/src/ui/components/version-badge.d.ts +7 -0
- package/dist/host/types/src/ui/hooks/use-page-visible-interval.d.ts +10 -0
- package/dist/host/types/src/ui/lib/activity-types.d.ts +15 -0
- package/dist/host/types/src/ui/lib/app-version.d.ts +2 -0
- package/dist/host/types/src/ui/lib/apply-ui-preset.d.ts +19 -0
- package/dist/host/types/src/ui/lib/connection-origin.d.ts +4 -5
- package/dist/host/types/src/ui/lib/create-id.d.ts +2 -0
- package/dist/host/types/src/ui/lib/database-transfer-url.d.ts +8 -0
- package/dist/host/types/src/ui/lib/desktop-updates.d.ts +29 -0
- package/dist/host/types/src/ui/lib/electron.d.ts +5 -0
- package/dist/host/types/src/ui/lib/host-connection-tabs.d.ts +14 -0
- package/dist/host/types/src/ui/lib/host-to-ssh-host.d.ts +4 -0
- package/dist/host/types/src/ui/lib/issue-url.d.ts +21 -0
- package/dist/host/types/src/ui/lib/local-adaptive-preferences.d.ts +9 -0
- package/dist/host/types/src/ui/lib/pane-nav-hotkey.d.ts +7 -0
- package/dist/host/types/src/ui/lib/plugin-i18n.d.ts +16 -0
- package/dist/host/types/src/ui/lib/role-label.d.ts +3 -0
- package/dist/host/types/src/ui/look/LookSettings.d.ts +8 -0
- package/dist/host/types/src/ui/look/layout-registry.d.ts +34 -0
- package/dist/host/types/src/ui/look/look-store.d.ts +29 -0
- package/dist/host/types/src/ui/look/resolve-look.d.ts +18 -0
- package/dist/host/types/src/ui/look/theme-css.d.ts +7 -0
- package/dist/host/types/src/ui/look/theme-registry.d.ts +41 -0
- package/dist/host/types/src/ui/look/use-look-theme.d.ts +12 -0
- package/dist/host/types/src/ui/manage/manage-requests.d.ts +34 -0
- package/dist/host/types/src/ui/plugin-host/loader.d.ts +2 -0
- package/dist/host/types/src/ui/plugin-host/scope.d.ts +6 -0
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +102 -0
- package/dist/host/types/src/ui/plugin-host/view-ownership.d.ts +1 -1
- package/dist/host/types/src/ui/plugins/PluginConsentPrompt.d.ts +20 -0
- package/dist/host/types/src/ui/plugins/PluginDetail.d.ts +7 -0
- package/dist/host/types/src/ui/plugins/PluginReleaseNotes.d.ts +14 -0
- package/dist/host/types/src/ui/plugins/PluginsTab.d.ts +1 -0
- package/dist/host/types/src/ui/plugins/RegistriesSection.d.ts +4 -0
- package/dist/host/types/src/ui/plugins/plugin-bits.d.ts +23 -0
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +116 -0
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +75 -0
- package/dist/host/types/src/ui/settings/appearance/appearance-controls.d.ts +53 -0
- package/dist/host/types/src/ui/settings/sync/LinkServerDialog.d.ts +12 -0
- package/dist/host/types/src/ui/settings/sync/ProxySettingsFields.d.ts +15 -0
- package/dist/host/types/src/ui/settings/sync/saved-servers.d.ts +3 -0
- package/dist/host/types/src/ui/shell/controller/ShellControllerProvider.d.ts +20 -0
- package/dist/host/types/src/ui/shell/controller/plugin-shell.d.ts +7 -0
- package/dist/host/types/src/ui/shell/controller/shell-store.d.ts +4 -0
- package/dist/host/types/src/ui/shell/controller/types.d.ts +156 -0
- package/dist/host/types/src/ui/shell/controller/use-hosts.d.ts +10 -0
- package/dist/host/types/src/ui/shell/controller/use-regions.d.ts +5 -0
- package/dist/host/types/src/ui/shell/controller/use-shell-keybindings.d.ts +12 -0
- package/dist/host/types/src/ui/shell/controller/use-shell-lifecycle.d.ts +17 -0
- package/dist/host/types/src/ui/shell/controller/use-split.d.ts +21 -0
- package/dist/host/types/src/ui/shell/controller/use-tabs.d.ts +70 -0
- package/dist/host/types/src/ui/shell/controller/use-workspace-layout.d.ts +4 -0
- package/dist/host/types/src/ui/shell/reconnect-tabs.d.ts +6 -0
- package/dist/host/types/src/ui/shell/shell-layout.d.ts +46 -0
- package/dist/host/types/src/ui/shell/split/EmptyPanePicker.d.ts +32 -0
- package/dist/host/types/src/ui/shell/split/SplitDropOverlay.d.ts +9 -0
- package/dist/host/types/src/ui/shell/split/SplitLayoutMenu.d.ts +16 -0
- package/dist/host/types/src/ui/shell/split/SplitView.d.ts +31 -0
- package/dist/host/types/src/ui/shell/split/split-drag.d.ts +58 -0
- package/dist/host/types/src/ui/shell/split/split-presets.d.ts +28 -0
- package/dist/host/types/src/ui/shell/split/split-tabs.d.ts +95 -0
- package/dist/host/types/src/ui/shell/split/split-targets.d.ts +30 -0
- package/dist/host/types/src/ui/shell/split/split-tree.d.ts +89 -0
- package/dist/host/types/src/ui/shell/stage/TabStage.d.ts +9 -0
- package/dist/host/types/src/ui/shell/stage/tab-stage-store.d.ts +24 -0
- package/dist/host/types/src/ui/shell/tab-close-guards.d.ts +9 -0
- package/dist/host/types/src/ui/shell/tab-handles.d.ts +8 -0
- package/dist/host/types/src/ui/shell/tabUtils.d.ts +15 -0
- package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +5 -0
- package/dist/host/types/src/ui/sidebar/HostManagerData.d.ts +4 -0
- package/dist/host/types/src/ui/sidebar/HostManagerTabs.d.ts +10 -4
- package/dist/host/types/src/ui/sidebar/build-host-tree.d.ts +25 -0
- package/dist/host/types/src/ui/sidebar/host-export-payload.d.ts +30 -0
- package/dist/host/ui.js +6238 -4054
- package/dist/host/{use-permissions-2P2TP6GY.js → use-permissions-6VSHHUMB.js} +2 -2
- package/dist/manifest.d.ts +41 -1
- package/dist/manifest.js +89 -1
- package/dist/testing/setup.js +1 -0
- package/dist/testing.d.ts +12 -1
- package/dist/testing.js +25 -1
- package/dist/theme-css.d.ts +11 -0
- package/dist/theme-css.js +142 -0
- package/dist/vitest-preset.d.ts +18 -0
- package/dist/vitest-preset.js +14 -3
- package/package.json +1 -1
- package/schema/manifest.schema.json +111 -8
- package/tsconfig.plugin.json +1 -0
- package/dist/host/chunk-YC2MAKSP.js +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.12
|
|
4
|
+
|
|
5
|
+
Plugin API 1.4.0.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `contributes.layouts` and `app.registerLayout()` for a whole-app layout, with the `ui:layout` capability. Needs `engine.api` 1.4
|
|
10
|
+
- `contributes.themes` for CSS-only themes, with the `ui:theme` capability. `termix-plugin validate` checks theme CSS and `pack` ships it
|
|
11
|
+
- `useShell()` gives a layout the live shell: tabs, splits, hosts, docks, the command palette and the registries. The shell's home tab is left out until some plugin registers a `dashboard` tab type
|
|
12
|
+
- `TabStage` and `ShellConfirmCapture` in `@termix-ssh/plugin-sdk/ui`, for a layout to place tabs
|
|
13
|
+
- `validateThemeCss()` in `@termix-ssh/plugin-sdk/manifest`
|
|
14
|
+
- `@termix-ssh/plugin-sdk/ui` also exports what a whole layout is built from: the API client, shared types, hooks, `useShellController()` and `APP_VERSION`. These reach into core, so they can change with a core release
|
|
15
|
+
- `@termix-ssh/plugin-sdk/testing-host` exports the registries and permission seam a layout's own tests fill directly
|
|
16
|
+
- A plugin that only holds themes needs no source: `termix-plugin build` writes an empty backend and `validate` does not ask for a frontend
|
|
17
|
+
- `termix-plugin validate` flags `ui:layout` or `ui:theme` with nothing contributed, and a layout plugin with no frontend
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- The manifest JSON schema lists the `notify:hub` and `plugins:read` capabilities
|
|
22
|
+
- Plugin tests and type checks work with the SDK linked from a local Termix checkout
|
|
23
|
+
|
|
24
|
+
## 1.0.11
|
|
25
|
+
|
|
26
|
+
Plugin API 1.3.0.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `ctx.auth.issueUserToken()` mints a short-lived session for a user, for calling Termix's own API as them from background work. Needs `users:impersonate` and `engine.api` 1.3
|
|
31
|
+
- `ctx.users.getLocale()` reads a user's UI language. Needs `users:read` and `engine.api` 1.3
|
|
32
|
+
|
|
3
33
|
## 1.0.10
|
|
4
34
|
|
|
5
35
|
### Changed
|
package/PLUGIN-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termix plugin guide
|
|
2
2
|
|
|
3
|
-
SDK 1.0.
|
|
3
|
+
SDK 1.0.12 | Plugin API 1.4.0
|
|
4
4
|
|
|
5
5
|
This guide is for anyone, person or AI coding tool, building a Termix plugin. It ships inside
|
|
6
6
|
`@termix-ssh/plugin-sdk`, so the copy in your `node_modules` always matches the SDK you have installed.
|
|
@@ -105,7 +105,7 @@ Before you call a change finished:
|
|
|
105
105
|
- [ ] `npm run build` passes
|
|
106
106
|
- [ ] `npm run format:check` passes
|
|
107
107
|
- [ ] `npx termix-plugin migrations --check` passes (you ran `npm run migrations` after changing tables)
|
|
108
|
-
- [ ] New strings are in `locales/en.json`
|
|
108
|
+
- [ ] New strings are in `locales/en.json`, and `plugin.name` / `plugin.description` match the manifest
|
|
109
109
|
- [ ] New routes have an `@openapi` block and a test, including without the permission
|
|
110
110
|
- [ ] New env vars are listed in `env` in the manifest
|
|
111
111
|
- [ ] `docs/` and the "About this plugin" section of `AGENTS.md` still match what the plugin does
|
|
@@ -175,7 +175,7 @@ Unknown fields are refused at every level, so a typo fails validation. The schem
|
|
|
175
175
|
|
|
176
176
|
`contributes` holds `tabs`, `panels`, `dashboardCards`, `actions`, `actionSlots`, `permissions`,
|
|
177
177
|
`settings`, `protocols`, `auth`, `syncEntities`, `keybindingActions`, `http`, `guest`, `guestViews`,
|
|
178
|
-
`uiPresets` and `
|
|
178
|
+
`uiPresets`, `uses`, `layouts` and `themes`. Frontend `register*` calls need their ids declared here first.
|
|
179
179
|
|
|
180
180
|
Capabilities and permissions are different things. A capability (`hosts:read`, with a colon) is what your
|
|
181
181
|
code may reach. A permission (`hello.use`, with a dot) is what a user may do.
|
|
@@ -225,6 +225,8 @@ Bundled as ESM for Node 22+ into `dist/backend.js`. Backend imports of your own
|
|
|
225
225
|
| `audit.record` | none | Write your own audit line |
|
|
226
226
|
| `sync` | none | Sync your tables to a linked desktop |
|
|
227
227
|
| `auth` | `auth:provide` | Login methods, second factors, SSH auth types |
|
|
228
|
+
| `auth.issueUserToken` | `users:impersonate` | A short-lived session for calling Termix's API as a user |
|
|
229
|
+
| `users.getLocale` | `users:read` | A user's UI language |
|
|
228
230
|
| `system` | `system:tls` | The server's HTTPS certificate |
|
|
229
231
|
| `plugins.list()` | `plugins:read` | Which plugins are installed |
|
|
230
232
|
| `asUser(userId, fn)` | always audited | Run background work as a user |
|
|
@@ -237,6 +239,13 @@ In a route the actor is the signed-in user, set by Termix. `ctx.currentActor()`
|
|
|
237
239
|
work with no request (a timer, an event) uses `ctx.asUser(userId, fn)`, which is audited and still applies
|
|
238
240
|
that user's permissions.
|
|
239
241
|
|
|
242
|
+
Background work that must call Termix's own HTTP API (the Termix CLI, say) asks for a session with
|
|
243
|
+
`ctx.auth.issueUserToken(userId, { purpose, ttlSeconds })` and sends it as `Authorization: Bearer <token>`.
|
|
244
|
+
It needs `users:impersonate` and `engine.api` `^1.3`, lasts 300 seconds by default and 900 at most, and is
|
|
245
|
+
audited as `plugin_user_token_issued`. The user's permissions apply, and it is refused on routes that change
|
|
246
|
+
their password, second factors, API keys or linked logins. `ctx.users.getLocale(userId)` gives the user's
|
|
247
|
+
UI language, like `zh_CN`, or null, for writing to them in it.
|
|
248
|
+
|
|
240
249
|
### Routes
|
|
241
250
|
|
|
242
251
|
Routes live at `/plugin-api/<id>/`, sockets at `/plugin-ws/<id>/`, on the main Termix port. You never need
|
|
@@ -469,6 +478,7 @@ your plugin's error boundary and Suspense.
|
|
|
469
478
|
| `registerComponent`, `registerExtension` | Offer a component by id, add to an extension point |
|
|
470
479
|
| `registerOnboardingStep` | A first run setup step |
|
|
471
480
|
| `registerLoginMethod`, `registerSecondFactorUI`, `registerSshAuthEditor` | Sign in UI |
|
|
481
|
+
| `registerLayout` | A whole-app layout (`contributes.layouts`, API 1.4) |
|
|
472
482
|
| `api` | Axios client for your `/plugin-api/<id>/` routes, auth included |
|
|
473
483
|
| `fetch(path)`, `wsUrl(path)` | Raw fetch for streams, socket URL and auth |
|
|
474
484
|
| `docs`, `tabs`, `confirm`, `listHosts`, `getHost`, `t`, `hasPermission`, `desktop` | Helpers |
|
|
@@ -476,7 +486,7 @@ your plugin's error boundary and Suspense.
|
|
|
476
486
|
|
|
477
487
|
Hooks from `@termix-ssh/plugin-sdk/frontend`: `useTranslation`, `usePermission`, `useSettings`, `useHost`,
|
|
478
488
|
`useHosts`, `usePluginApi`, `useCurrentUser`, `useTheme`, `useToast`, `useDocsUrl`, `useTabs`,
|
|
479
|
-
`useSlotContributions`, `useSshAuthTypes`, `useExtensions`.
|
|
489
|
+
`useSlotContributions`, `useSshAuthTypes`, `useExtensions`, `useShell`.
|
|
480
490
|
|
|
481
491
|
Shared from Termix and never bundled: `react`, `react-dom`, `i18next`, `react-i18next`, `sonner` and the
|
|
482
492
|
SDK's `frontend` and `ui`. Anything else, like `lucide-react`, is bundled. A host your code gets only carries
|
|
@@ -484,9 +494,98 @@ your own host settings, under `host.pluginSettings[<id>]`.
|
|
|
484
494
|
|
|
485
495
|
Slots you can fill include `terminal.toolbar`, `terminal.toolbarStatus`, `terminal.sidePanel`,
|
|
486
496
|
`terminal.overlay`, `dashboard.counters`, `dashboard.hostMetrics`, `admin.userTabs`, `hosts.importMenu`,
|
|
487
|
-
`hosts.panel`, `credentials.badges`, `shell.overlay` and `tab.menu`. See
|
|
497
|
+
`hosts.panel`, `credentials.badges`, `shell.overlay`, `shell.rightEdge` and `tab.menu`. See
|
|
488
498
|
https://docs.termix.site/develop/more-extension-points.
|
|
489
499
|
|
|
500
|
+
## Layouts and themes
|
|
501
|
+
|
|
502
|
+
A plugin can replace the whole app layout or add themes (API 1.4). Users pick both in Settings, Appearance;
|
|
503
|
+
admins set the default for everyone.
|
|
504
|
+
|
|
505
|
+
A layout is declared in `contributes.layouts` (`id`, `label`, optional `description` and `defaultTheme`) and
|
|
506
|
+
needs the `ui:layout` capability, which admins see as high risk because a layout draws every screen. Register
|
|
507
|
+
it in `activate`:
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
import { useShell, type TermixApp } from "@termix-ssh/plugin-sdk/frontend";
|
|
511
|
+
import {
|
|
512
|
+
ShellConfirmCapture,
|
|
513
|
+
SurfaceScope,
|
|
514
|
+
TabStage,
|
|
515
|
+
} from "@termix-ssh/plugin-sdk/ui";
|
|
516
|
+
|
|
517
|
+
function MyLayout() {
|
|
518
|
+
const shell = useShell();
|
|
519
|
+
if (!shell) return null;
|
|
520
|
+
return (
|
|
521
|
+
<div className="flex h-dvh w-screen flex-col bg-background">
|
|
522
|
+
<header>
|
|
523
|
+
{/* tabs from shell.tabs.topLevel, shell.palette.open() and so on */}
|
|
524
|
+
</header>
|
|
525
|
+
<SurfaceScope kind="tab">
|
|
526
|
+
<ShellConfirmCapture />
|
|
527
|
+
<TabStage />
|
|
528
|
+
</SurfaceScope>
|
|
529
|
+
</div>
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function activate(app: TermixApp) {
|
|
534
|
+
app.registerLayout({
|
|
535
|
+
id: "my-layout",
|
|
536
|
+
label: "layout.name",
|
|
537
|
+
component: MyLayout,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
- `useShell()` is the live shell with nothing drawn: tabs, splits, hosts, the left and right docks, the
|
|
543
|
+
command palette, the registries as hooks and the user. Core keeps all the state, so switching layouts keeps
|
|
544
|
+
every session.
|
|
545
|
+
- `TabStage` is where tabs show. Place it anywhere; tabs are never remounted when it moves, so a terminal
|
|
546
|
+
keeps its connection. Wrap it in `SurfaceScope kind="tab"` with a `ShellConfirmCapture` inside, so
|
|
547
|
+
close questions and inline views show over the tab area. Overlays and onboarding stay in core.
|
|
548
|
+
- The layout draws the command palette. Core keeps whether it is open (`palette.isOpen`, Ctrl+K calls
|
|
549
|
+
`palette.open()`) and what is in it (`usePaletteEntries`, `usePaletteGroups`, rail items, hosts).
|
|
550
|
+
- The shell always keeps a home tab with the id and type `dashboard`. `useShell()` leaves it out until some
|
|
551
|
+
plugin registers a `dashboard` tab type, so a layout without a home screen has no empty tab to draw.
|
|
552
|
+
- Settings, Manage and the host editor belong to Classic Layout. A layout without them should still render
|
|
553
|
+
`LookSettings` somewhere so people can switch back. `tabs.openSingleton("plugins")` opens core's own
|
|
554
|
+
Plugins tab in any layout. Switching layouts closes the tabs the old layout's plugin declared.
|
|
555
|
+
- `mobileComponent` is used on phones and narrow windows. The desktop app always gets `component`.
|
|
556
|
+
- Only the chosen layout's plugin is loaded. If it fails to load or crashes twice, Termix shows a repair
|
|
557
|
+
screen where people can switch, reset, update, turn it off or (for a bundled plugin) restore the copy that
|
|
558
|
+
shipped. `?safe-layout=1` opens it any time.
|
|
559
|
+
- Termix has no layout of its own. The default is the bundled Classic Layout plugin
|
|
560
|
+
(https://github.com/Termix-SSH/Plugin-Classic-Layout), which is also a full example. It owns the rail,
|
|
561
|
+
sidebar, tab bar, docks, Dashboard, Manage, Settings and command palette, registers the `dashboard`,
|
|
562
|
+
`host-manager` and `settings` tab types, and offers the host list and dashboard as the components
|
|
563
|
+
`classic-layout.hostTree` and `classic-layout.dashboard` for other plugins to place with `PluginComponent`.
|
|
564
|
+
It reads the shell with `useShellController()` from `@termix-ssh/plugin-sdk/ui`, which exposes more than
|
|
565
|
+
`useShell()` and may change with Termix. A third party layout should start from `useShell()`.
|
|
566
|
+
- Example Top Bar Layout (https://github.com/Termix-SSH/Plugin-Example-Topbar-Layout) is a small third party
|
|
567
|
+
style layout built only on `useShell()`, `TabStage` and `@termix-ssh/plugin-sdk/ui`, with a phone version.
|
|
568
|
+
|
|
569
|
+
A theme is CSS only. Declare it in `contributes.themes` (`id`, `label`, `mode` light or dark, `css` path,
|
|
570
|
+
optional `swatch` colors for the picker) with the `ui:theme` capability. The id becomes a class on `<html>`,
|
|
571
|
+
so the file may only hold rules whose selectors start with `.<id>`, `:root.<id>` or `html.<id>`, optionally
|
|
572
|
+
inside `@media` or `@supports`. `@import`, `url()` to another host and other at-rules are refused by
|
|
573
|
+
`termix-plugin validate` and by the server. Set the same tokens core's themes set (`--background`,
|
|
574
|
+
`--foreground`, `--card`, `--border`, `--muted` and the rest).
|
|
575
|
+
|
|
576
|
+
A plugin that only holds themes needs no code. Leave out `src/`: `termix-plugin build` writes an empty backend and
|
|
577
|
+
`validate` does not ask for a frontend. Keep the CSS in `themes/` and a test that checks it with
|
|
578
|
+
`validateThemeCss()`. Notes:
|
|
579
|
+
|
|
580
|
+
- A theme with the id of a built-in (`light` or `dark`) replaces core's basic copy while the plugin runs.
|
|
581
|
+
- The first theme a bundled default-look plugin lists is the default for new installs.
|
|
582
|
+
- Themes show in the picker in the order core lists its own, then the plugin's order.
|
|
583
|
+
- A theme someone saved that no longer exists shows as System. It comes back when the plugin does.
|
|
584
|
+
- Accent color, UI font, font size and density are not part of a theme. Core applies them on top.
|
|
585
|
+
- Bundled Classic Themes (https://github.com/Termix-SSH/Plugin-Classic-Themes) is a full example, and
|
|
586
|
+
Example Theme (https://github.com/Termix-SSH/Plugin-Example-Theme) a small one with one light and one dark
|
|
587
|
+
theme.
|
|
588
|
+
|
|
490
589
|
## Looking native
|
|
491
590
|
|
|
492
591
|
Build UI from `@termix-ssh/plugin-sdk/ui` so it matches Termix and follows the theme. The full list of
|
|
@@ -538,6 +637,20 @@ Plurals use `_one` and `_other` with `t("hosts", { count })`. Other languages go
|
|
|
538
637
|
`locales/translated/<code>.json` (like `de_DE.json`) with the same keys. Keep `{{placeholders}}` as they are.
|
|
539
638
|
Untranslated strings show in English.
|
|
540
639
|
|
|
640
|
+
Termix shows your plugin's name and description in the plugin manager, the setup picker and settings.
|
|
641
|
+
Put them in `locales/en.json` as `plugin.name` and `plugin.description`, with the same text as `name` and
|
|
642
|
+
`description` in the manifest, so they get translated too. Without them, the English manifest text shows in
|
|
643
|
+
every language.
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{
|
|
647
|
+
"plugin": {
|
|
648
|
+
"name": "Host Metrics",
|
|
649
|
+
"description": "Live CPU, memory, disk, network and GPU stats."
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
541
654
|
## Docs
|
|
542
655
|
|
|
543
656
|
`docs/index.md` is the first page: what the plugin does in a sentence or two, how to turn it on, how to use
|
|
@@ -740,37 +853,39 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
740
853
|
|
|
741
854
|
## Capabilities
|
|
742
855
|
|
|
743
|
-
| Capability | Risk | What it allows
|
|
744
|
-
| -------------------- | -------- |
|
|
745
|
-
| `credentials:read` | critical | Read stored secrets for hosts the user can reach
|
|
746
|
-
| `system:tls` | critical | Replace the HTTPS certificate, answer CA domain checks
|
|
747
|
-
| `ssh:connect` | high | Run any command on hosts, with the user's access
|
|
748
|
-
| `process:spawn` | high | Run programs on the Termix server
|
|
749
|
-
| `users:write` | high | Add users and change their roles
|
|
750
|
-
| `users:impersonate` | high |
|
|
751
|
-
| `db:core-refs` | high | Query core users, roles and hosts tables (read only)
|
|
752
|
-
| `auth:provide` | high | Add login methods, second factors, SSH auth types
|
|
753
|
-
| `device:serial` | high | Open serial and USB ports on the server
|
|
754
|
-
| `notify:hub` | high | Receive every alert (the alerts hub only)
|
|
755
|
-
| `
|
|
756
|
-
| `
|
|
757
|
-
| `credentials:
|
|
758
|
-
| `
|
|
759
|
-
| `network:
|
|
760
|
-
| `network:
|
|
761
|
-
| `
|
|
762
|
-
| `
|
|
763
|
-
| `
|
|
764
|
-
| `
|
|
765
|
-
| `
|
|
766
|
-
| `
|
|
767
|
-
| `
|
|
768
|
-
| `
|
|
769
|
-
| `
|
|
770
|
-
| `
|
|
771
|
-
| `
|
|
772
|
-
| `
|
|
773
|
-
| `
|
|
856
|
+
| Capability | Risk | What it allows |
|
|
857
|
+
| -------------------- | -------- | ------------------------------------------------------- |
|
|
858
|
+
| `credentials:read` | critical | Read stored secrets for hosts the user can reach |
|
|
859
|
+
| `system:tls` | critical | Replace the HTTPS certificate, answer CA domain checks |
|
|
860
|
+
| `ssh:connect` | high | Run any command on hosts, with the user's access |
|
|
861
|
+
| `process:spawn` | high | Run programs on the Termix server |
|
|
862
|
+
| `users:write` | high | Add users and change their roles |
|
|
863
|
+
| `users:impersonate` | high | Act as another user, or get a session for them, audited |
|
|
864
|
+
| `db:core-refs` | high | Query core users, roles and hosts tables (read only) |
|
|
865
|
+
| `auth:provide` | high | Add login methods, second factors, SSH auth types |
|
|
866
|
+
| `device:serial` | high | Open serial and USB ports on the server |
|
|
867
|
+
| `notify:hub` | high | Receive every alert (the alerts hub only) |
|
|
868
|
+
| `ui:layout` | high | Replace the whole app layout |
|
|
869
|
+
| `hosts:write` | medium | Add and edit hosts |
|
|
870
|
+
| `credentials:use` | medium | Connect with saved credentials without seeing them |
|
|
871
|
+
| `credentials:write` | medium | Save new credentials |
|
|
872
|
+
| `network:outbound` | medium | Outbound HTTP through `ctx.fetch` |
|
|
873
|
+
| `network:serve` | medium | HTTP routes and sockets |
|
|
874
|
+
| `network:broadcast` | medium | Local network broadcasts, like Wake-on-LAN |
|
|
875
|
+
| `users:read` | medium | Usernames, roles and UI language |
|
|
876
|
+
| `events:core` | medium | Listen to core events |
|
|
877
|
+
| `notify:send` | medium | Send alerts |
|
|
878
|
+
| `audit:read` | medium | Read the audit log |
|
|
879
|
+
| `desktop:window` | medium | Open windows in the desktop app |
|
|
880
|
+
| `hosts:read` | low | Read hosts the user can see |
|
|
881
|
+
| `db:own` | low | Your own tables |
|
|
882
|
+
| `kv:own` | low | Small key value state |
|
|
883
|
+
| `files:own` | low | Your own folder on disk |
|
|
884
|
+
| `secrets:own` | low | Your own encrypted secrets |
|
|
885
|
+
| `settings:read-core` | low | Read Termix server settings |
|
|
886
|
+
| `plugins:read` | low | See installed plugins |
|
|
887
|
+
| `ui:surface` | low | Add tabs, panels and settings |
|
|
888
|
+
| `ui:theme` | low | Add color themes, CSS only |
|
|
774
889
|
|
|
775
890
|
An update that adds a capability waits for an admin to approve it.
|
|
776
891
|
|
package/cli/commands/build.mjs
CHANGED
|
@@ -5,7 +5,12 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import * as esbuild from "esbuild";
|
|
6
6
|
import { BACKEND_EXTERNALS, FRONTEND_EXTERNALS } from "../lib/externals.mjs";
|
|
7
7
|
import { staticUrlImports } from "../lib/static-url-imports.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
readManifest,
|
|
10
|
+
resolveEntry,
|
|
11
|
+
copyDir,
|
|
12
|
+
isThemeOnly,
|
|
13
|
+
} from "../lib/plugin-dir.mjs";
|
|
9
14
|
import { openapi } from "./openapi.mjs";
|
|
10
15
|
import { splitByCoreClasses } from "../lib/css-classes.mjs";
|
|
11
16
|
|
|
@@ -232,32 +237,41 @@ export async function build({ cwd }) {
|
|
|
232
237
|
fs.mkdirSync(outDir, { recursive: true });
|
|
233
238
|
|
|
234
239
|
const backendEntry = resolveEntry(cwd, BACKEND_ENTRIES);
|
|
235
|
-
if (!backendEntry) {
|
|
240
|
+
if (!backendEntry && !isThemeOnly(manifest)) {
|
|
236
241
|
throw new Error(
|
|
237
242
|
`${pluginId}: no backend entry (looked for ${BACKEND_ENTRIES.join(", ")})`,
|
|
238
243
|
);
|
|
239
244
|
}
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
246
|
+
if (backendEntry) {
|
|
247
|
+
await esbuild.build({
|
|
248
|
+
entryPoints: [backendEntry],
|
|
249
|
+
outfile: path.join(outDir, "backend.js"),
|
|
250
|
+
bundle: true,
|
|
251
|
+
format: "esm",
|
|
252
|
+
platform: "node",
|
|
253
|
+
target: "node22",
|
|
254
|
+
sourcemap: true,
|
|
255
|
+
logLevel: "warning",
|
|
256
|
+
// A native dependency's compiled .node binary is resolved by the
|
|
257
|
+
// package's own relative paths, which break once esbuild inlines its JS
|
|
258
|
+
// elsewhere. Declaring it in nativeDependencies keeps it a real
|
|
259
|
+
// node_modules import instead, resolved at runtime like a host-provided
|
|
260
|
+
// package.
|
|
261
|
+
external: [...BACKEND_EXTERNALS, ...(manifest.nativeDependencies ?? [])],
|
|
262
|
+
loader: BINARY_LOADERS,
|
|
263
|
+
// Bundled CJS deps still call require() for Node builtins at runtime.
|
|
264
|
+
// ESM has no ambient require, so give esbuild's require shim a real one.
|
|
265
|
+
banner: { js: REQUIRE_BANNER },
|
|
266
|
+
});
|
|
267
|
+
} else {
|
|
268
|
+
// Core loads every plugin through a backend entry, so a theme only
|
|
269
|
+
// plugin gets an empty one.
|
|
270
|
+
fs.writeFileSync(
|
|
271
|
+
path.join(outDir, "backend.js"),
|
|
272
|
+
"export function activate() {}\n",
|
|
273
|
+
);
|
|
274
|
+
}
|
|
261
275
|
|
|
262
276
|
for (const worker of readWorkerEntries(cwd, pluginId)) {
|
|
263
277
|
await esbuild.build({
|
package/cli/commands/pack.mjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readManifest } from "../lib/plugin-dir.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
collectFiles,
|
|
6
|
+
createTmxplug,
|
|
7
|
+
themeFiles,
|
|
8
|
+
TMXPLUG_CONTENTS,
|
|
9
|
+
} from "../lib/tmxplug.mjs";
|
|
5
10
|
import { sha256 } from "../lib/signing.mjs";
|
|
6
11
|
import { validate } from "./validate.mjs";
|
|
7
12
|
|
|
@@ -32,7 +37,12 @@ export async function pack({ cwd, args = [] }) {
|
|
|
32
37
|
fs.mkdirSync(outDir, { recursive: true });
|
|
33
38
|
const outfile = path.join(outDir, `${pluginId}-${version}.tmxplug`);
|
|
34
39
|
|
|
35
|
-
const archive = createTmxplug(
|
|
40
|
+
const archive = createTmxplug(
|
|
41
|
+
cwd,
|
|
42
|
+
collectFiles(cwd, [
|
|
43
|
+
...new Set([...TMXPLUG_CONTENTS, ...themeFiles(manifest)]),
|
|
44
|
+
]),
|
|
45
|
+
);
|
|
36
46
|
fs.writeFileSync(outfile, archive);
|
|
37
47
|
|
|
38
48
|
console.log(`packed ${path.basename(outfile)}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { readManifest } from "../lib/plugin-dir.mjs";
|
|
4
|
+
import { readManifest, isThemeOnly } from "../lib/plugin-dir.mjs";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The manifest rules live in src/manifest.ts, which the server uses too, so
|
|
@@ -17,7 +17,7 @@ export async function loadSdkModule(name) {
|
|
|
17
17
|
|
|
18
18
|
export async function validate({ cwd }) {
|
|
19
19
|
const raw = readManifest(cwd);
|
|
20
|
-
const { parseManifest } = await loadSdkModule("manifest");
|
|
20
|
+
const { parseManifest, validateThemeCss } = await loadSdkModule("manifest");
|
|
21
21
|
const { errors } = parseManifest(raw);
|
|
22
22
|
|
|
23
23
|
const problems = [...errors];
|
|
@@ -25,7 +25,8 @@ export async function validate({ cwd }) {
|
|
|
25
25
|
// The manifest names files. They have to be there.
|
|
26
26
|
const referenced = [
|
|
27
27
|
raw.backend ?? "dist/backend.js",
|
|
28
|
-
|
|
28
|
+
// A theme only plugin has no frontend code.
|
|
29
|
+
...(isThemeOnly(raw) ? [] : [raw.frontend ?? "dist/frontend.js"]),
|
|
29
30
|
raw.locales ?? "locales",
|
|
30
31
|
];
|
|
31
32
|
for (const rel of referenced) {
|
|
@@ -34,6 +35,8 @@ export async function validate({ cwd }) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
problems.push(...validateThemeFiles(cwd, raw, validateThemeCss));
|
|
39
|
+
problems.push(...validateLookCapabilities(cwd, raw));
|
|
37
40
|
problems.push(
|
|
38
41
|
...(await validateMigrations(cwd, raw.id ?? path.basename(cwd))),
|
|
39
42
|
);
|
|
@@ -53,6 +56,63 @@ export async function validate({ cwd }) {
|
|
|
53
56
|
console.log(`ok ${raw.id ?? path.basename(cwd)}`);
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
/** Every declared theme file exists and holds only CSS a theme may hold. */
|
|
60
|
+
export function validateThemeFiles(cwd, raw, validateThemeCss) {
|
|
61
|
+
const problems = [];
|
|
62
|
+
const themes = raw?.contributes?.themes;
|
|
63
|
+
if (!Array.isArray(themes)) return problems;
|
|
64
|
+
for (const theme of themes) {
|
|
65
|
+
if (typeof theme?.css !== "string" || typeof theme?.id !== "string") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const file = path.join(cwd, theme.css);
|
|
69
|
+
if (!fs.existsSync(file)) {
|
|
70
|
+
problems.push(`theme ${theme.id}: ${theme.css} does not exist`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const error of validateThemeCss(
|
|
74
|
+
fs.readFileSync(file, "utf8"),
|
|
75
|
+
theme.id,
|
|
76
|
+
)) {
|
|
77
|
+
problems.push(`theme ${theme.id}: ${error}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return problems;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* ui:layout and ui:theme must match what the manifest contributes, both
|
|
85
|
+
* ways. The manifest check already flags a contribution without its
|
|
86
|
+
* capability; this flags a capability with nothing behind it, and a layout
|
|
87
|
+
* with no frontend to register it.
|
|
88
|
+
*/
|
|
89
|
+
export function validateLookCapabilities(cwd, raw) {
|
|
90
|
+
const problems = [];
|
|
91
|
+
const capabilities = Array.isArray(raw?.capabilities) ? raw.capabilities : [];
|
|
92
|
+
const layouts = raw?.contributes?.layouts;
|
|
93
|
+
const themes = raw?.contributes?.themes;
|
|
94
|
+
const hasLayouts = Array.isArray(layouts) && layouts.length > 0;
|
|
95
|
+
const hasThemes = Array.isArray(themes) && themes.length > 0;
|
|
96
|
+
if (capabilities.includes("ui:layout") && !hasLayouts) {
|
|
97
|
+
problems.push('"ui:layout" is declared but contributes.layouts is empty');
|
|
98
|
+
}
|
|
99
|
+
if (capabilities.includes("ui:theme") && !hasThemes) {
|
|
100
|
+
problems.push('"ui:theme" is declared but contributes.themes is empty');
|
|
101
|
+
}
|
|
102
|
+
if (hasLayouts) {
|
|
103
|
+
const entries = ["index.tsx", "index.ts", "index.mjs", "index.js"];
|
|
104
|
+
const frontend = entries.some((entry) =>
|
|
105
|
+
fs.existsSync(path.join(cwd, "src", "frontend", entry)),
|
|
106
|
+
);
|
|
107
|
+
if (!frontend && raw.frontend === undefined) {
|
|
108
|
+
problems.push(
|
|
109
|
+
"contributes.layouts needs a frontend that calls app.registerLayout",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return problems;
|
|
114
|
+
}
|
|
115
|
+
|
|
56
116
|
/** Env vars core sets for everyone, so a plugin need not list them. */
|
|
57
117
|
const CORE_ENV = new Set([
|
|
58
118
|
"DATA_DIR",
|
package/cli/lib/plugin-dir.mjs
CHANGED
|
@@ -14,6 +14,17 @@ export function readManifest(cwd) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/** The first of these that exists, or null. */
|
|
17
|
+
/** Only themes: CSS files and no code, so no source entry is needed. */
|
|
18
|
+
export function isThemeOnly(manifest) {
|
|
19
|
+
const capabilities = manifest.capabilities ?? [];
|
|
20
|
+
return (
|
|
21
|
+
capabilities.length > 0 &&
|
|
22
|
+
capabilities.every((capability) => capability === "ui:theme") &&
|
|
23
|
+
Array.isArray(manifest.contributes?.themes) &&
|
|
24
|
+
manifest.contributes.themes.length > 0
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export function resolveEntry(cwd, candidates) {
|
|
18
29
|
for (const candidate of candidates) {
|
|
19
30
|
const full = path.join(cwd, candidate);
|
package/cli/lib/tmxplug.mjs
CHANGED
|
@@ -2,6 +2,15 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import zlib from "node:zlib";
|
|
4
4
|
|
|
5
|
+
/** Theme CSS files a manifest declares, which travel with the plugin too. */
|
|
6
|
+
export function themeFiles(manifest) {
|
|
7
|
+
const themes = manifest?.contributes?.themes;
|
|
8
|
+
if (!Array.isArray(themes)) return [];
|
|
9
|
+
return themes
|
|
10
|
+
.map((theme) => theme?.css)
|
|
11
|
+
.filter((css) => typeof css === "string");
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
/** What a .tmxplug holds, in the order the spec lists it. */
|
|
6
15
|
export const TMXPLUG_CONTENTS = [
|
|
7
16
|
"manifest.json",
|
package/dist/backend.d.ts
CHANGED
|
@@ -1223,6 +1223,18 @@ export interface PluginSecondFactor {
|
|
|
1223
1223
|
/** Removes the user's enrolment. Called by the admin reset. */
|
|
1224
1224
|
reset?: (userId: string) => Promise<void>;
|
|
1225
1225
|
}
|
|
1226
|
+
export interface PluginUserTokenOptions {
|
|
1227
|
+
/** Why the token exists, kept in the audit log and on the session. */
|
|
1228
|
+
purpose: string;
|
|
1229
|
+
/** Seconds until it expires. Defaults to 300 and is capped at 900. */
|
|
1230
|
+
ttlSeconds?: number;
|
|
1231
|
+
}
|
|
1232
|
+
export interface PluginUserToken {
|
|
1233
|
+
/** Send as `Authorization: Bearer <token>` to core's own API. */
|
|
1234
|
+
token: string;
|
|
1235
|
+
/** ISO time the session ends. */
|
|
1236
|
+
expiresAt: string;
|
|
1237
|
+
}
|
|
1226
1238
|
/**
|
|
1227
1239
|
* Login methods, second factors and SSH auth types. Every register call needs
|
|
1228
1240
|
* auth:provide and a matching contributes.auth entry, and is undone on
|
|
@@ -1278,6 +1290,22 @@ export interface PluginAuth {
|
|
|
1278
1290
|
* delete a provider people still sign in with.
|
|
1279
1291
|
*/
|
|
1280
1292
|
countLinkedUsers: (provider: string) => Promise<number>;
|
|
1293
|
+
/**
|
|
1294
|
+
* A short-lived session for an existing user, for calling core's own HTTP
|
|
1295
|
+
* API as them from work with no request behind it, like a scheduled task
|
|
1296
|
+
* driving the Termix CLI. The user's RBAC applies unchanged. Needs
|
|
1297
|
+
* users:impersonate, not auth:provide, and every call is audited.
|
|
1298
|
+
*
|
|
1299
|
+
* The session is marked with the plugin and purpose. It cannot change the
|
|
1300
|
+
* user's password, second factors, API keys or linked logins, so it can
|
|
1301
|
+
* never outlive itself.
|
|
1302
|
+
*/
|
|
1303
|
+
issueUserToken: (userId: string, options: PluginUserTokenOptions) => Promise<PluginUserToken>;
|
|
1304
|
+
}
|
|
1305
|
+
/** What a plugin may learn about a user. Needs users:read. */
|
|
1306
|
+
export interface PluginUsers {
|
|
1307
|
+
/** The user's UI language, like "zh_CN", or null when they never chose one. */
|
|
1308
|
+
getLocale: (userId: string) => Promise<string | null>;
|
|
1281
1309
|
}
|
|
1282
1310
|
export interface PluginOpenIsolatedWindowRequest {
|
|
1283
1311
|
/** http(s) only. Refused when it points anywhere else. */
|
|
@@ -1738,8 +1766,13 @@ export interface PluginContext {
|
|
|
1738
1766
|
readonly hosts: PluginHosts;
|
|
1739
1767
|
/** SSH through core's connect pipeline. Needs ssh:connect and credentials:use. */
|
|
1740
1768
|
readonly ssh: PluginSsh;
|
|
1741
|
-
/**
|
|
1769
|
+
/**
|
|
1770
|
+
* Login methods, second factors and SSH auth types. Needs auth:provide,
|
|
1771
|
+
* except issueUserToken, which needs users:impersonate.
|
|
1772
|
+
*/
|
|
1742
1773
|
readonly auth: PluginAuth;
|
|
1774
|
+
/** What a plugin may learn about a user. Needs users:read. */
|
|
1775
|
+
readonly users: PluginUsers;
|
|
1743
1776
|
/** Opens Electron windows outside the main renderer. Needs desktop:window. */
|
|
1744
1777
|
readonly desktop: PluginDesktop;
|
|
1745
1778
|
/** Host protocol credentials and the user's saved SSH keys. See PluginCredentials. */
|
|
@@ -1763,8 +1796,9 @@ export interface PluginContext {
|
|
|
1763
1796
|
* no request behind it. Always audited, and every core API inside still
|
|
1764
1797
|
* applies that user's RBAC.
|
|
1765
1798
|
*
|
|
1766
|
-
* Needs users:impersonate. That and a `userId` option
|
|
1767
|
-
* secrets.getShared are the only ways a plugin can
|
|
1799
|
+
* Needs users:impersonate. That, auth.issueUserToken and a `userId` option
|
|
1800
|
+
* on services.get or secrets.getShared are the only ways a plugin can act
|
|
1801
|
+
* as a user it names.
|
|
1768
1802
|
*/
|
|
1769
1803
|
asUser: <T>(userId: string, fn: () => Promise<T> | T) => Promise<T>;
|
|
1770
1804
|
/** The acting user for the current call, when there is one. */
|
package/dist/capabilities.js
CHANGED
|
@@ -35,6 +35,7 @@ export const CAPABILITY_CATALOG = [
|
|
|
35
35
|
entry("auth:provide", "high"),
|
|
36
36
|
entry("device:serial", "high"),
|
|
37
37
|
entry("notify:hub", "high"),
|
|
38
|
+
entry("ui:layout", "high"),
|
|
38
39
|
entry("hosts:write", "medium"),
|
|
39
40
|
entry("credentials:use", "medium"),
|
|
40
41
|
entry("credentials:write", "medium"),
|
|
@@ -54,6 +55,7 @@ export const CAPABILITY_CATALOG = [
|
|
|
54
55
|
entry("settings:read-core", "low"),
|
|
55
56
|
entry("plugins:read", "low"),
|
|
56
57
|
entry("ui:surface", "low"),
|
|
58
|
+
entry("ui:theme", "low"),
|
|
57
59
|
];
|
|
58
60
|
export const CAPABILITY_IDS = CAPABILITY_CATALOG.map((capability) => capability.id);
|
|
59
61
|
const BY_ID = new Map(CAPABILITY_CATALOG.map((capability) => [capability.id, capability]));
|