@voltius/plugin-types 0.15.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Voltius
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @voltius/plugin-types
2
+
3
+ TypeScript definitions for the [Voltius](https://github.com/VoltiusApp/voltius) plugin API.
4
+
5
+ ```bash
6
+ npm install --save-dev @voltius/plugin-types
7
+ ```
8
+
9
+ ```ts
10
+ import type { PluginAPI } from "@voltius/plugin-types";
11
+
12
+ export default function register(api: PluginAPI): () => void {
13
+ const dispose = api.ui.registerRightPanelSection({ /* ... */ });
14
+ return () => dispose();
15
+ }
16
+ ```
17
+
18
+ Types only — there is no runtime code. The host provides the implementation.
19
+
20
+ **Versions track the app.** `@voltius/plugin-types@0.14.1` describes the API in Voltius 0.14.1.
21
+
22
+ `ui.d.ts` declares the `@voltius/ui` module (the host components plugins may import). It is
23
+ included automatically; you do not need to reference it.
24
+
25
+ Generated from `src/plugins/api.ts` — see
26
+ [developing plugins](https://docs.voltius.app/plugins/developing) and the
27
+ [template repo](https://github.com/VoltiusApp/voltius-plugin-template).
package/index.d.ts ADDED
@@ -0,0 +1,738 @@
1
+ // GENERATED by scripts/gen-plugin-types.mjs — do not edit by hand.
2
+ // Source of truth: src/plugins/api.ts in VoltiusApp/voltius.
3
+ /// <reference path="./ui.d.ts" />
4
+ import type { ReactNode } from "react";
5
+
6
+ export interface SerialConnectParams {
7
+ sessionId: string;
8
+ port: string;
9
+ baud: number;
10
+ dataBits?: number;
11
+ parity?: string;
12
+ stopBits?: number;
13
+ flowControl?: string;
14
+ }
15
+
16
+ export interface JumpHost {
17
+ id: string;
18
+ // Live reference to a managed connection. Host/port/username/credentials are
19
+ // resolved dynamically from this connection at use time — see resolveJumpHosts.
20
+ connection_id: string;
21
+ // Snapshot fields, kept only as a fallback when the referenced connection is
22
+ // missing (deleted) or for jump hosts imported from external formats (e.g.
23
+ // Termius) that have no managed connection. Not written for managed references.
24
+ host?: string;
25
+ port?: number;
26
+ username?: string;
27
+ identity_id?: string;
28
+ }
29
+
30
+ export interface TerminalTheme {
31
+ background: string;
32
+ foreground: string;
33
+ cursor: string;
34
+ selectionBackground: string;
35
+ black: string; red: string; green: string; yellow: string;
36
+ blue: string; magenta: string; cyan: string; white: string;
37
+ brightBlack: string; brightRed: string; brightGreen: string;
38
+ brightYellow: string; brightBlue: string; brightMagenta: string;
39
+ brightCyan: string; brightWhite: string;
40
+ }
41
+
42
+ export interface UITheme {
43
+ bgTerminal: string; // titlebar + terminal bg
44
+ bgStatusBar: string; // terminal status bar bg
45
+ bgBase: string; // homepage/main bg
46
+ bgToolbar: string; // toolbar/sidebar surfaces
47
+ bgCard: string; // host cards
48
+ bgCardHover: string; // host cards hovered
49
+ bgCardAvatar: string; // host card default avatar bg
50
+ bgInput: string; // inputs/search
51
+ bgInputHover: string; // buttons/inputs hovered
52
+ bgElevated: string; // hover states / elevated surfaces
53
+ bgModal: string; // omni/modal bg
54
+ border: string;
55
+ borderHover: string;
56
+ textDim: string; // dimmest (placeholders)
57
+ textMuted: string; // icons, secondary
58
+ textSecondary: string;
59
+ textPrimary: string;
60
+ textBright: string;
61
+ accent: string;
62
+ accentHover: string;
63
+ tabBg: string; // inactive SSH tab background
64
+ tabActiveBg: string;
65
+ tabActiveText: string;
66
+ tabActiveBorder: string;
67
+ vaultTabBg: string; // vault/home tab — inactive background
68
+ vaultTabActiveBg: string; // vault/home tab — active background
69
+ statusConnected: string;
70
+ statusError: string;
71
+ statusConnecting: string;
72
+ statusWarning: string;
73
+ textNotice: string; // notice/info boxes text and icon
74
+ }
75
+
76
+ export interface AppTheme {
77
+ id: string;
78
+ name: string;
79
+ builtIn: boolean;
80
+ uiFontFamily: string;
81
+ uiFontSize: number;
82
+ terminalFontFamily: string;
83
+ terminalFontSize: number;
84
+ ui: UITheme;
85
+ terminal: TerminalTheme;
86
+ }
87
+
88
+ export type Locale = "en" | "fr" | "ru" | "zh";
89
+
90
+
91
+ // ─── Types exposés aux plugins ─────────────────────────────────────────────
92
+
93
+ export interface PluginConnection {
94
+ id: string;
95
+ name?: string;
96
+ host: string;
97
+ port: number;
98
+ username: string;
99
+ auth_type: "password" | "key";
100
+ tags: string[];
101
+ identity_id?: string;
102
+ jump_hosts?: JumpHost[];
103
+ // Display-only fields — already present at runtime (runtime.ts:389 returns
104
+ // full Connection records cast to PluginConnection[]); exposed here so the
105
+ // agent UI can render a real per-host avatar. Optional and additive.
106
+ connection_type?: "ssh" | "serial" | "ftp";
107
+ icon?: string;
108
+ distro?: string;
109
+ serial_port?: string;
110
+ }
111
+
112
+ export interface PluginConnectionInput {
113
+ name?: string;
114
+ host: string;
115
+ port: number;
116
+ username: string;
117
+ auth_type: "password" | "key";
118
+ tags?: string[];
119
+ identity_id?: string;
120
+ jump_hosts?: JumpHost[];
121
+ }
122
+
123
+ export interface PluginKey {
124
+ id: string;
125
+ name?: string;
126
+ key_type?: string;
127
+ tags: string[];
128
+ }
129
+
130
+ export interface PluginIdentity {
131
+ id: string;
132
+ name?: string;
133
+ username: string;
134
+ key_id?: string;
135
+ tags: string[];
136
+ }
137
+
138
+ export interface OmniCommand {
139
+ id: string;
140
+ label: string;
141
+ icon: string;
142
+ keywords?: string[];
143
+ section?: string;
144
+ /** Optional keyboard shortcut. Format: "ctrl+k", "meta+shift+p". First-registered wins on conflict. */
145
+ keybinding?: string;
146
+ /** ID of a core shortcut to resolve as the hint (reactive, updates when user rebinds). */
147
+ shortcutId?: string;
148
+ execute: () => void | Promise<void>;
149
+ }
150
+
151
+ /** A label that may be a function, re-resolved by the host on locale change.
152
+ * A plain string is frozen at registration time. */
153
+ export type PluginLabel = string | (() => string);
154
+
155
+ export interface SettingsPage {
156
+ id: string;
157
+ label: PluginLabel;
158
+ icon: string;
159
+ component: React.FC;
160
+ }
161
+
162
+ export interface RightPanelSection {
163
+ id: string;
164
+ label: PluginLabel;
165
+ icon: string;
166
+ component: React.FC;
167
+ /** Opt-in: this section drives the terminal status bar's high-CPU indicator
168
+ * and its metrics stream. Explicit flag rather than an id check, so a plugin
169
+ * can't inherit the host integration by squatting another plugin's section id. */
170
+ providesHostMetrics?: boolean;
171
+ /** Opt-in: this section owns an in-panel search bar that Ctrl+F should focus
172
+ * when the section is open, via the "voltius:focus-panel-search" event. */
173
+ providesPanelSearch?: boolean;
174
+ /** Rail position, ascending. Sections without one sort last; ties break on `id`.
175
+ * Registration order is NOT stable — it follows the on-disk read order of the
176
+ * seeded plugin directory and changes after any uninstall/reinstall — so a
177
+ * section that wants a fixed rail slot must declare it here. */
178
+ order?: number;
179
+ }
180
+
181
+ /** Nav-stack entries a plugin may push via `pushMobileScreen`. Each member's
182
+ * `kind` must exist as a "panel-<kind>" variant of mobileNavCore's MobileScreen
183
+ * union — runtime.ts's translator switch is exhaustively checked against that
184
+ * union, so adding a member here without a matching host variant is a type
185
+ * error, not a silent no-op at runtime. */
186
+ export type PluginMobileNavEntry = {
187
+ kind: "docker-logs";
188
+ sessionId: string;
189
+ containerId: string;
190
+ containerName: string;
191
+ };
192
+
193
+ /** Props the host passes into a registered mobile screen's `render`. Extra
194
+ * navigation params (e.g. docker-logs' containerId/containerName) ride along
195
+ * as additional keys — see `pushMobileScreen`. */
196
+ export interface MobileScreenProps {
197
+ sessionId: string;
198
+ /** Pop this screen off the mobile nav stack. MobilePanelHeader itself can't
199
+ * cross the plugin boundary (it reaches into the host's nav store), so the
200
+ * screen must render its own header chrome and wire this to its back button. */
201
+ onBack: () => void;
202
+ [key: string]: unknown;
203
+ }
204
+
205
+ export interface MobileScreen {
206
+ id: string;
207
+ /** Screen key MobileShell looks up on navigation, e.g. "docker", "metrics". */
208
+ kind: string;
209
+ render: React.FC<MobileScreenProps>;
210
+ }
211
+
212
+ export interface GlobalPanel {
213
+ id: string;
214
+ /** Rendered at shell level (not session-scoped). Host drives open/close. */
215
+ component: React.FC<{ open: boolean; onClose: () => void }>;
216
+ }
217
+
218
+ export interface PluginSession {
219
+ id: string;
220
+ connectionId: string;
221
+ connectionName: string;
222
+ status: string;
223
+ type: string;
224
+ /** Local sessions only: the shell path/name to use for a spawned exec PTY. */
225
+ localShell?: string;
226
+ }
227
+
228
+ export type PluginTheme = AppTheme;
229
+
230
+ // ─── Notification types ────────────────────────────────────────────────────
231
+
232
+ export type ToastSeverity = 'info' | 'success' | 'warning' | 'error';
233
+
234
+ export interface ToastOptions {
235
+ severity?: ToastSeverity;
236
+ duration?: number;
237
+ action?: { label: string; onClick: () => void };
238
+ }
239
+
240
+ export interface ProgressOptions {
241
+ indeterminate?: boolean;
242
+ cancellable?: boolean;
243
+ }
244
+
245
+ export interface ProgressHandle {
246
+ update(value: number, message?: string): void;
247
+ finish(message?: string): void;
248
+ error(message: string): void;
249
+ cancel(): void;
250
+ }
251
+
252
+ export interface BannerOptions {
253
+ severity?: ToastSeverity;
254
+ actions?: Array<{ label: string; onClick: () => void }>;
255
+ dismissable?: boolean;
256
+ flashToast?: boolean;
257
+ }
258
+
259
+ export interface BannerHandle {
260
+ dismiss(): void;
261
+ update(message: string): void;
262
+ }
263
+
264
+ // ─── UI Contribution types ─────────────────────────────────────────────────
265
+
266
+ /** A single action item contributed by a plugin to a UI slot. */
267
+ export interface ContributedAction {
268
+ label: string;
269
+ icon?: string;
270
+ onClick: () => void;
271
+ divider?: boolean;
272
+ danger?: boolean;
273
+ /** Keyboard shortcut hint displayed on the right in context menus */
274
+ shortcut?: string;
275
+ /** If provided, item is only shown when this returns true. Errors are treated as false. */
276
+ when?: (context: unknown) => boolean;
277
+ }
278
+
279
+ /** Named UI slots where plugins can inject actions. */
280
+ export type UISlot =
281
+ | "connection.contextMenu"
282
+ | "connection.panelActions"
283
+ | "key.contextMenu"
284
+ | "key.panelActions"
285
+ | "identity.contextMenu"
286
+ | "identity.panelActions"
287
+ | "portForwardingRule.contextMenu"
288
+ | "home.bgContextMenu"
289
+ | "keychain.bgContextMenu"
290
+ | "home.toolbar.hostMenu"
291
+ | "settings.vaults";
292
+
293
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
+ export type UIContributionFactory = (ctx: any) => ContributedAction[];
295
+
296
+ export type UIStatusBarSlot = "terminal.statusBar.right" | "titlebar.right";
297
+
298
+ export interface TerminalStatusBarContributionContext {
299
+ sessionId: string;
300
+ sessionType: "ssh" | "local" | "serial";
301
+ connectionId: string;
302
+ connectionName?: string;
303
+ sessionStatus: "connecting" | "connected" | "disconnected" | "error";
304
+ connection?: PluginConnection;
305
+ serialConfig?: SerialConnectParams;
306
+ dimensions?: { cols: number; rows: number };
307
+ }
308
+
309
+ export type UIStatusBarContributionFactory = (ctx: TerminalStatusBarContributionContext) => ReactNode;
310
+
311
+ export type StreamKind = "metrics" | "processes" | "docker-logs" | "docker-stack-logs";
312
+
313
+ export interface StreamsAPI {
314
+ /** Start a session-scoped stream. Returns a streamId. */
315
+ start(kind: StreamKind, opts: Record<string, unknown>): Promise<string>;
316
+ /** Stop a stream. No-op for an unknown id. */
317
+ stop(streamId: string): Promise<void>;
318
+ /** Subscribe to a started stream's snapshots. Resolves to an unsubscribe fn. */
319
+ on<T>(streamId: string, cb: (snapshot: T) => void): Promise<() => void>;
320
+ }
321
+
322
+ /** Host metrics — built on top of api.streams' "metrics" kind. GATED (metrics:read). */
323
+ export interface MetricsAPI {
324
+ start(sessionId: string, isRemote: boolean): Promise<string>;
325
+ stop(streamId: string): Promise<void>;
326
+ onSnapshot<T>(streamId: string, cb: (snapshot: T) => void): Promise<() => void>;
327
+ getSystemInfo(sessionId: string, sessionType: string, sessionName?: string): Promise<unknown>;
328
+ }
329
+
330
+ /** Process listing/kill — built on top of api.streams' "processes" kind. GATED, split
331
+ * two ways: processes:read covers start/onSnapshot/stop; processes:manage covers kill. */
332
+ export interface ProcessesAPI {
333
+ start(sessionId: string, isRemote: boolean): Promise<string>;
334
+ stop(streamId: string): Promise<void>;
335
+ onSnapshot<T>(streamId: string, cb: (snapshot: T) => void): Promise<() => void>;
336
+ kill(sessionId: string, pid: number, isRemote: boolean, force: boolean): Promise<void>;
337
+ }
338
+
339
+ /** Not gated — a pure KDF over caller-supplied input, grants no access to host secrets. */
340
+ export interface CryptoAPI {
341
+ /** Derive a 32-byte key from a passphrase and hex salt. Returns hex. */
342
+ deriveKey(passphrase: string, saltHex: string): Promise<string>;
343
+ }
344
+
345
+ /** Locales the host ships. A plugin's catalog may cover any subset — "en" should
346
+ * always be present, since it is the fallback when the active locale is missing.
347
+ * Re-exports the host's own `Locale` union (type-only, erased at build — this
348
+ * doesn't pull `@/stores/localeStore` into the plugin bundle) so a future host
349
+ * locale addition flows through here automatically instead of drifting out of sync. */
350
+ export type PluginLocale = Locale;
351
+
352
+ /** A flat key → template map for one locale. Values may contain "{{var}}" placeholders. */
353
+ export type PluginLocaleCatalog = Record<string, string>;
354
+
355
+ export type PluginI18nCatalog = Partial<Record<PluginLocale, PluginLocaleCatalog>>;
356
+
357
+ /**
358
+ * Not gated — reading/resolving UI strings grants no host access. Each plugin owns
359
+ * its own catalog (registered here, not in the host's locale files) so a third-party
360
+ * plugin can ship translations exactly the way a first-party one does.
361
+ */
362
+ export interface I18nAPI {
363
+ /** Register (or replace) this plugin's translation catalog. Call once at load,
364
+ * before rendering anything that resolves keys. */
365
+ register(catalog: PluginI18nCatalog): void;
366
+ /** Resolve `key` against the host's active locale. Falls back to the "en" entry,
367
+ * then to `key` itself (visible, never blank) if neither has it. */
368
+ t(key: string, vars?: Record<string, string | number>): string;
369
+ /** The host's current active locale. */
370
+ getLocale(): PluginLocale;
371
+ /** Fires whenever the host's active locale changes. Re-call `t()` and re-render
372
+ * on each firing — this does not itself trigger a React re-render. Returns an
373
+ * unsubscribe function. */
374
+ onLocaleChange(cb: (locale: PluginLocale) => void): () => void;
375
+ }
376
+
377
+ /** Proxmox VE LXC management. GATED, split two ways: proxmox:read covers
378
+ * list/snapshots.list; proxmox:manage covers everything else. Only functions
379
+ * against SSH sessions. */
380
+ export interface ProxmoxAPI {
381
+ lxc: {
382
+ list(sessionId: string): Promise<unknown[]>;
383
+ action(sessionId: string, vmid: number, action: string): Promise<void>;
384
+ /** Opens a pct-exec shell into the container and returns the new session's id.
385
+ * vmName is display-only — used for the resulting terminal tab's label. */
386
+ openShell(sessionId: string, vmid: number, vmName?: string): Promise<string>;
387
+ snapshots: {
388
+ list(sessionId: string, vmid: number): Promise<unknown[]>;
389
+ create(sessionId: string, vmid: number, name: string, description?: string): Promise<void>;
390
+ rollback(sessionId: string, vmid: number, name: string): Promise<void>;
391
+ remove(sessionId: string, vmid: number, name: string): Promise<void>;
392
+ };
393
+ };
394
+ }
395
+
396
+ /** Where a docker command runs. Replaces the repeated (sessionId, isRemote, localShell)
397
+ * triple the underlying commands take — a transposed boolean in a positional call is
398
+ * silent; this shape makes every call site self-describing and tsc-checked. */
399
+ export interface DockerTarget {
400
+ sessionId: string;
401
+ isRemote: boolean;
402
+ localShell: string | null;
403
+ }
404
+
405
+ /**
406
+ * Docker container/image/volume/network/stack management. GATED, split two ways
407
+ * (kipavy ruling): docker:read covers every list/services/checkUpdate verb and all
408
+ * of logs.*; docker:manage covers everything that mutates or destroys, including
409
+ * exec.open (an interactive shell inside a container is full control, not a read).
410
+ */
411
+ export interface DockerAPI {
412
+ containers: {
413
+ list(t: DockerTarget): Promise<unknown[]>;
414
+ action(t: DockerTarget, containerId: string, action: string): Promise<void>;
415
+ /** Reconstructs the `docker run` command for the container. `command` is the
416
+ * container's image ref, passed through to the backend command as `image`. */
417
+ runCommand(t: DockerTarget, containerId: string, command: string): Promise<string>;
418
+ };
419
+ images: {
420
+ list(t: DockerTarget): Promise<unknown[]>;
421
+ remove(t: DockerTarget, imageId: string): Promise<void>;
422
+ pull(t: DockerTarget, image: string): Promise<void>;
423
+ checkUpdate(t: DockerTarget, imageId: string): Promise<unknown>;
424
+ /** Pulls `image` and, when `recreate` is set, recreates the containers using it. */
425
+ update(t: DockerTarget, imageId: string, recreate: boolean): Promise<unknown>;
426
+ recreateContainers(t: DockerTarget, imageId: string): Promise<unknown>;
427
+ prune(t: DockerTarget): Promise<string>;
428
+ };
429
+ volumes: {
430
+ list(t: DockerTarget): Promise<unknown[]>;
431
+ remove(t: DockerTarget, name: string): Promise<void>;
432
+ prune(t: DockerTarget): Promise<string>;
433
+ };
434
+ networks: {
435
+ list(t: DockerTarget): Promise<unknown[]>;
436
+ remove(t: DockerTarget, id: string): Promise<void>;
437
+ prune(t: DockerTarget): Promise<string>;
438
+ };
439
+ stacks: {
440
+ list(t: DockerTarget): Promise<unknown[]>;
441
+ services(t: DockerTarget, stack: string): Promise<unknown[]>;
442
+ action(t: DockerTarget, stack: string, action: string): Promise<void>;
443
+ update(t: DockerTarget, stack: string): Promise<void>;
444
+ };
445
+ logs: {
446
+ start(t: DockerTarget, containerId: string, tail: number): Promise<string>;
447
+ startStack(t: DockerTarget, stack: string, tail: number): Promise<string>;
448
+ stop(streamId: string): Promise<void>;
449
+ /** Payload is a DockerLogLine ({ line, stream }), not a bare string — kept generic like StreamsAPI.on. */
450
+ on<T>(streamId: string, cb: (payload: T) => void): Promise<() => void>;
451
+ };
452
+ system: { prune(t: DockerTarget): Promise<string> };
453
+ exec: {
454
+ /** Opens an interactive shell into the container and returns the new session's id.
455
+ * containerName is display-only — used for the resulting terminal tab's label. */
456
+ open(t: DockerTarget, containerId: string, containerName?: string): Promise<string>;
457
+ };
458
+ }
459
+
460
+ // ─── API principale ────────────────────────────────────────────────────────
461
+
462
+ export interface PluginAPI {
463
+ pluginId: string;
464
+ /** Returns true if this plugin is currently enabled in the registry. */
465
+ isActive(): boolean;
466
+
467
+ // SSH keys (requires keys:*)
468
+ keys: {
469
+ list(): Promise<PluginKey[]>;
470
+ /** Creates a key entry and stores private/public content in the vault. */
471
+ create(data: { name?: string; key_type?: string; tags?: string[] }, privateKey: string, publicKey?: string): Promise<PluginKey>;
472
+ delete(id: string): Promise<void>;
473
+ };
474
+
475
+ // Identities (requires identities:*)
476
+ identities: {
477
+ list(): Promise<PluginIdentity[]>;
478
+ create(data: { name?: string; username: string; key_id?: string; tags?: string[] }): Promise<PluginIdentity>;
479
+ delete(id: string): Promise<void>;
480
+ };
481
+
482
+ // Connections (requires connections:*)
483
+ connections: {
484
+ list(): Promise<PluginConnection[]>;
485
+ get(id: string): Promise<PluginConnection | null>;
486
+ create(data: PluginConnectionInput): Promise<PluginConnection>;
487
+ update(id: string, data: Partial<PluginConnectionInput>): Promise<void>;
488
+ delete(id: string): Promise<void>;
489
+ bulkImport(items: PluginConnectionInput[]): Promise<PluginConnection[]>;
490
+ subscribe(cb: (connections: PluginConnection[]) => void): () => void;
491
+ };
492
+
493
+ // Vault — plugin-scoped secrets (requires vault:*)
494
+ vault: {
495
+ get(key: string): Promise<string | null>;
496
+ set(key: string, value: string): Promise<void>;
497
+ delete(key: string): Promise<void>;
498
+ };
499
+
500
+ // Themes (requires "themes")
501
+ themes: {
502
+ register(theme: PluginTheme): void;
503
+ unregister(id: string): void;
504
+ };
505
+
506
+ // OmniSearch (requires "omni-commands")
507
+ omni: {
508
+ register(command: OmniCommand): () => void;
509
+ unregister(id: string): void;
510
+ };
511
+
512
+ // UI — extension points
513
+ ui: {
514
+ registerSettingsPage(page: SettingsPage): () => void;
515
+ registerRightPanelSection(section: RightPanelSection): () => void;
516
+ /** Mount a global, shell-level panel (not session-scoped). Returns cleanup. */
517
+ registerGlobalPanel(panel: GlobalPanel): () => void;
518
+ /** Contribute a full-screen mobile view for `screen.kind`. Uninstalling or
519
+ * disabling the plugin removes it, same as registerRightPanelSection does
520
+ * on desktop. Returns cleanup. */
521
+ registerMobileScreen(screen: MobileScreen): () => void;
522
+ /** Push another mobile screen onto the nav stack (e.g. docker's container
523
+ * list pushing its logs view). Writes to the mobile nav store regardless
524
+ * of platform — harmless on desktop, since MobileShell is never mounted
525
+ * there. */
526
+ pushMobileScreen(entry: PluginMobileNavEntry): void;
527
+ /** Switch the mobile shell to its terminal tab — e.g. after opening an exec
528
+ * shell. Writes to the mobile nav store regardless of platform — harmless
529
+ * on desktop, since MobileShell is never mounted there. */
530
+ focusMobileTerminal(): void;
531
+ /** Inject action items into a named UI slot. Returns a cleanup function. */
532
+ registerContribution<C = unknown>(slot: UISlot, fn: (ctx: C) => ContributedAction[]): () => void;
533
+ /** Render a React widget in the terminal status bar's right-side slot. Returns a cleanup function. */
534
+ registerStatusBarItem(slot: UIStatusBarSlot, fn: UIStatusBarContributionFactory): () => void;
535
+ unregister(id: string): void;
536
+ /** Switch the app's active navigation section. */
537
+ setActiveNav(id: string): void;
538
+ /** Publish a plain, serialisable state snapshot for host UI to read, keyed
539
+ * by `<pluginId>::<key>`. Host surfaces subscribe to this instead of
540
+ * importing the plugin's runtime module. Cleared on unload/disable. */
541
+ publishState(key: string, value: unknown): void;
542
+ };
543
+
544
+ // Plugin-scoped key-value storage
545
+ storage: {
546
+ get<T>(key: string): Promise<T | null>;
547
+ set<T>(key: string, value: T): Promise<void>;
548
+ delete(key: string): Promise<void>;
549
+ };
550
+
551
+ // HTTP (requiert "http")
552
+ http: {
553
+ get<T>(url: string, opts?: RequestInit): Promise<T>;
554
+ post<T>(url: string, body: unknown, opts?: RequestInit): Promise<T>;
555
+ /** Streaming request. Returns a Response with a ReadableStream body (for SSE/LLM streaming). */
556
+ stream(url: string, init?: RequestInit): Promise<Response>;
557
+ };
558
+
559
+ // Filesystem restricted to home (requires "fs")
560
+ fs: {
561
+ readText(path: string): Promise<string>;
562
+ writeText(path: string, content: string): Promise<void>;
563
+ exists(path: string): Promise<boolean>;
564
+ /** Polling-based file watch. Calls cb when content changes. Returns cleanup fn. */
565
+ watch(path: string, cb: () => void, opts?: { intervalMs?: number }): () => void;
566
+ };
567
+
568
+ // Event bus (always available)
569
+ events: {
570
+ on(event: string, handler: (data: unknown) => void): () => void;
571
+ emit(event: string, data?: unknown): void;
572
+ };
573
+
574
+ // Notifications (requires "notifications")
575
+ notifications: {
576
+ toast(message: string, opts?: ToastOptions): void;
577
+ progress(title: string, opts?: ProgressOptions): ProgressHandle;
578
+ banner(message: string, opts?: BannerOptions): BannerHandle;
579
+ };
580
+
581
+ // Plugin-scoped logger
582
+ log: {
583
+ info(msg: string, ...args: unknown[]): void;
584
+ warn(msg: string, ...args: unknown[]): void;
585
+ error(msg: string, ...args: unknown[]): void;
586
+ };
587
+
588
+ // Sessions (requires sessions:read / sessions:write)
589
+ sessions: {
590
+ /** Returns current sessions snapshot. */
591
+ list(): PluginSession[];
592
+ /** The session backing the active terminal tab, or null if there is none. */
593
+ getActive(): PluginSession | null;
594
+ /** Fires when a session becomes connected. */
595
+ onConnected(cb: (session: PluginSession) => void): () => void;
596
+ /** Fires when a connected session is removed or disconnected. */
597
+ onDisconnected(cb: (session: PluginSession) => void): () => void;
598
+ /** Fires when the user switches to a different terminal tab. */
599
+ onActivated(cb: (session: PluginSession) => void): () => void;
600
+ /** Send a command to a session. Runtime appends \n. Requires sessions:write. */
601
+ sendCommand(sessionId: string, cmd: string): Promise<void>;
602
+ /** Open (connect) a saved connection by id. Resolves to the new sessionId. Requires sessions:write. */
603
+ open(connectionId: string): Promise<string>;
604
+ /** Close (disconnect) a session by id. Requires sessions:write. */
605
+ close(sessionId: string): Promise<void>;
606
+ };
607
+
608
+ // Terminal output — GATED (first-party only). Requires terminal:read / terminal:stream.
609
+ terminal: {
610
+ /** Last `maxLines` lines of a session's buffer as text (default 200). */
611
+ readSnapshot(sessionId: string, maxLines?: number): string;
612
+ /** The session's current selection as text, or "" if nothing is selected. */
613
+ readSelection(sessionId: string): string;
614
+ /** Subscribe to live decoded output for a session. Resolves to an unsubscribe fn. */
615
+ onOutput(sessionId: string, cb: (text: string) => void): Promise<() => void>;
616
+ };
617
+
618
+ // Keychain — GATED (first-party only). OS-local, never synced.
619
+ // Requires keychain:read / keychain:write.
620
+ keychain: {
621
+ /** Read a value from the OS keychain. Returns null if unset. */
622
+ get(key: string): Promise<string | null>;
623
+ /** Write a value to the OS keychain. */
624
+ set(key: string, value: string): Promise<void>;
625
+ /** Delete a value from the OS keychain (no-op if absent). */
626
+ delete(key: string): Promise<void>;
627
+ };
628
+
629
+ // Session-scoped streams (metrics, processes, docker logs) — GATED per kind.
630
+ streams: StreamsAPI;
631
+
632
+ // Host metrics domain wrapper over streams — GATED (metrics:read).
633
+ metrics: MetricsAPI;
634
+
635
+ // Process listing/kill domain wrapper over streams — GATED, split
636
+ // processes:read (start/onSnapshot/stop) / processes:manage (kill).
637
+ processes: ProcessesAPI;
638
+
639
+ // Key derivation (requires crypto:derive). Not gated — pure KDF over caller input.
640
+ crypto: CryptoAPI;
641
+
642
+ // Plugin-owned UI translation catalog (requires "ui"). Not gated.
643
+ i18n: I18nAPI;
644
+
645
+ // Proxmox VE LXC management — GATED, split
646
+ // proxmox:read (list/snapshots.list) / proxmox:manage (everything else).
647
+ proxmox: ProxmoxAPI;
648
+
649
+ // Docker container/image/volume/network/stack management — GATED, split
650
+ // docker:read (list/services/checkUpdate/logs.*) / docker:manage (everything else).
651
+ docker: DockerAPI;
652
+
653
+ // Lifecycle hooks (always available)
654
+ lifecycle: {
655
+ /** Fires when an SSH/local session transitions to "connected". */
656
+ onConnectionEstablished(cb: (conn: PluginConnection) => void): () => void;
657
+ /** Fires when a connected session is removed or becomes disconnected. */
658
+ onConnectionClosed(cb: (conn: PluginConnection) => void): () => void;
659
+ /** Fires when the user switches to a different terminal tab. */
660
+ onSessionActivated(cb: (session: PluginSession) => void): () => void;
661
+ /** Fires when this plugin's own storage.set() is called. */
662
+ onSettingsChanged(cb: (key: string, value: unknown) => void): () => void;
663
+ /** Fires before the app closes. Must resolve within 5 seconds. */
664
+ onBeforeQuit(cb: () => void | Promise<void>): () => void;
665
+ /** Resolves once the login-time server sync has completed (or immediately for local/offline users). */
666
+ waitForLoginSync(): Promise<void>;
667
+ };
668
+
669
+ // Sync / blob storage (requires sync:read / sync:write)
670
+ sync: {
671
+ /** Read a plugin-scoped blob from local storage. Returns null if not set. */
672
+ getBlob(key: string): Promise<Uint8Array | null>;
673
+ /** Write a plugin-scoped blob to local storage. Max 1 MB. */
674
+ setBlob(key: string, data: Uint8Array): Promise<void>;
675
+ /**
676
+ * Register a callback that fires after a sync completes and the stored
677
+ * blob for `key` has changed. Note: cross-device sync of plugin blobs
678
+ * requires future Tauri backend support — currently fires on local changes only.
679
+ */
680
+ onRemoteChange(key: string, cb: (data: Uint8Array) => void): () => void;
681
+ /** Reload a named in-app store (e.g. "connections", "identities", "keys"). */
682
+ triggerReload(storeKey: string): Promise<void>;
683
+ /**
684
+ * Export the full app state (connections, keys, identities, secrets) as a
685
+ * base64-encoded XChaCha20-Poly1305 encrypted blob — same format as cloud sync.
686
+ * encKey: 64-char hex string (32 bytes). Requires sync:write.
687
+ */
688
+ exportState(encKey: string, deviceId: string): Promise<string>;
689
+ /**
690
+ * CRDT-merge one or more remote encrypted blobs into local state, then
691
+ * reload all entity stores. blobs: base64-encoded (same format as exportState).
692
+ * Requires sync:write.
693
+ */
694
+ importStates(encKey: string, blobs: string[]): Promise<void>;
695
+ };
696
+
697
+ // Inter-plugin communication (always available)
698
+ plugins: {
699
+ /** Publish this plugin's public API surface so other plugins can consume it. */
700
+ expose(publicApi: unknown): void;
701
+ /** Get another plugin's exposed API. Returns null if not loaded or not exposed. */
702
+ getApi(pluginId: string): unknown | null;
703
+ };
704
+ }
705
+
706
+ export type PluginRegisterFn = (api: PluginAPI) => (() => void) | void;
707
+
708
+ // ─── Settings schema ───────────────────────────────────────────────────────
709
+
710
+ export interface PluginConfigField {
711
+ type: "string" | "number" | "boolean" | "select";
712
+ default: unknown;
713
+ description: string;
714
+ /** Overrides the auto-derived label (the host humanizes the key by default). */
715
+ label?: string;
716
+ options?: string[]; // for select
717
+ secret?: boolean; // render as password input
718
+ min?: number; // for number: minimum (also clamps on save)
719
+ max?: number; // for number: maximum (also clamps on save)
720
+ }
721
+
722
+ export interface PluginManifest {
723
+ id: string;
724
+ name: string;
725
+ version: string;
726
+ /** Minimum app version required to run this plugin. Falls back to the app version
727
+ * at build time when the manifest omits it. */
728
+ minAppVersion?: string;
729
+ description?: string;
730
+ permissions: string[];
731
+ defaultEnabled?: boolean;
732
+ /** Hidden in the plugin list on mobile (uses host-only resources, e.g. local fs). */
733
+ desktopOnly?: boolean;
734
+ contributes?: {
735
+ configuration?: Record<string, PluginConfigField>;
736
+ };
737
+ }
738
+
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@voltius/plugin-types",
3
+ "version": "0.15.0",
4
+ "description": "TypeScript definitions for the Voltius plugin API",
5
+ "types": "index.d.ts",
6
+ "files": [
7
+ "index.d.ts",
8
+ "ui.d.ts",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/VoltiusApp/voltius.git",
16
+ "directory": "packages/plugin-types"
17
+ },
18
+ "homepage": "https://docs.voltius.app/plugins/developing",
19
+ "keywords": [
20
+ "voltius",
21
+ "plugin",
22
+ "types",
23
+ "ssh",
24
+ "sftp"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "peerDependencies": {
30
+ "@types/react": ">=18"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "@types/react": {
34
+ "optional": true
35
+ }
36
+ }
37
+ }
package/ui.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ // Components the host exposes to plugins. Externalized at build time so you use
2
+ // the host's own instances — see the build script in package.json.
3
+ declare module "@voltius/ui" {
4
+ import type { ComponentType, ReactNode } from "react";
5
+ export const Icon: ComponentType<{ icon: string; width?: number | string; className?: string }>;
6
+ export const InfoTooltip: ComponentType<{ text: string; children?: ReactNode }>;
7
+ export const BottomSheet: ComponentType<{ title?: string; onClose: () => void; children?: ReactNode }>;
8
+ export function useAutosave<T>(value: T, save: (v: T) => void | Promise<void>, delayMs?: number): void;
9
+ }