From e99f91f4f9600e5efbda1be8104ff5d9b394228c Mon Sep 17 00:00:00 2001 From: kipavy Date: Mon, 3 Aug 2026 16:24:33 +0000 Subject: [PATCH] Use the published @voltius/plugin-types instead of vendored types --- README.md | 15 +- package-lock.json | 16 + package.json | 5 +- tsconfig.json | 9 +- types/index.d.ts | 737 ------------------------------------------ types/voltius-ui.d.ts | 9 - 6 files changed, 31 insertions(+), 760 deletions(-) delete mode 100644 types/index.d.ts delete mode 100644 types/voltius-ui.d.ts diff --git a/README.md b/README.md index 9c1c030..738f01d 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Then load it in the app: | `manifest.json` | id, permissions, and a `contributes.configuration` setting the host renders | | `src/index.tsx` | the `register` entry point and its cleanup function | | `src/Panel.tsx` | the right-panel component | -| `types/index.d.ts` | generated `PluginAPI` types — see below | | `scripts/check-externals.mjs` | fails the build if you import something the host can't provide | ## The build flags are not optional @@ -53,10 +52,16 @@ trusting a copy here, so it also catches the host changing the contract. ## Types -`types/index.d.ts` is generated from `src/plugins/api.ts` in the Voltius repo by -`scripts/gen-plugin-types.mjs` there. To refresh it against a newer release, re-run that script -and replace the file. `tsconfig.json` maps it to the `@voltius/plugin-types` specifier the docs -use. +`PluginAPI` comes from the [`@voltius/plugin-types`](https://www.npmjs.com/package/@voltius/plugin-types) +dev dependency: + +```sh +npm install --save-dev @voltius/plugin-types +``` + +Its version tracks the app, so `@voltius/plugin-types@0.15.0` is the API Voltius 0.15.0 exposes — +install the one matching the oldest release you support, and keep `minAppVersion` in +`manifest.json` in step. One install also covers the `@voltius/ui` types. ## Docs diff --git a/package-lock.json b/package-lock.json index 153198c..88356f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "devDependencies": { "@types/react": "^19.0.0", + "@voltius/plugin-types": "^0.15.0", "esbuild": "^0.25.0", "typescript": "^5.6.0" } @@ -465,6 +466,21 @@ "csstype": "^3.2.2" } }, + "node_modules/@voltius/plugin-types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@voltius/plugin-types/-/plugin-types-0.15.0.tgz", + "integrity": "sha512-vWDTQVcTwFeGFYTslBxkMHmAqRNcbJBsCuSqOBsGNvsrR8x/MTJZm0kY+T+530hn6RLvg9dZD1mtm6zgnUrp+Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": ">=18" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", diff --git a/package.json b/package.json index 2f1aadb..f08b621 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,9 @@ "check": "node scripts/check-externals.mjs" }, "devDependencies": { + "@types/react": "^19.0.0", + "@voltius/plugin-types": "^0.15.0", "esbuild": "^0.25.0", - "typescript": "^5.6.0", - "@types/react": "^19.0.0" + "typescript": "^5.6.0" } } diff --git a/tsconfig.json b/tsconfig.json index ee9c5c2..5b7e13a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,12 +7,7 @@ "jsx": "react-jsx", "strict": true, "noEmit": true, - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "@voltius/plugin-types": ["./types/index.d.ts"], - "@voltius/ui": ["./types/voltius-ui.d.ts"] - } + "skipLibCheck": true }, - "include": ["src", "types"] + "include": ["src"] } diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index c7a128f..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,737 +0,0 @@ -// GENERATED by scripts/gen-plugin-types.mjs — do not edit by hand. -// Source of truth: src/plugins/api.ts in VoltiusApp/voltius. -import type { ReactNode } from "react"; - -export interface SerialConnectParams { - sessionId: string; - port: string; - baud: number; - dataBits?: number; - parity?: string; - stopBits?: number; - flowControl?: string; -} - -export interface JumpHost { - id: string; - // Live reference to a managed connection. Host/port/username/credentials are - // resolved dynamically from this connection at use time — see resolveJumpHosts. - connection_id: string; - // Snapshot fields, kept only as a fallback when the referenced connection is - // missing (deleted) or for jump hosts imported from external formats (e.g. - // Termius) that have no managed connection. Not written for managed references. - host?: string; - port?: number; - username?: string; - identity_id?: string; -} - -export interface TerminalTheme { - background: string; - foreground: string; - cursor: string; - selectionBackground: string; - black: string; red: string; green: string; yellow: string; - blue: string; magenta: string; cyan: string; white: string; - brightBlack: string; brightRed: string; brightGreen: string; - brightYellow: string; brightBlue: string; brightMagenta: string; - brightCyan: string; brightWhite: string; -} - -export interface UITheme { - bgTerminal: string; // titlebar + terminal bg - bgStatusBar: string; // terminal status bar bg - bgBase: string; // homepage/main bg - bgToolbar: string; // toolbar/sidebar surfaces - bgCard: string; // host cards - bgCardHover: string; // host cards hovered - bgCardAvatar: string; // host card default avatar bg - bgInput: string; // inputs/search - bgInputHover: string; // buttons/inputs hovered - bgElevated: string; // hover states / elevated surfaces - bgModal: string; // omni/modal bg - border: string; - borderHover: string; - textDim: string; // dimmest (placeholders) - textMuted: string; // icons, secondary - textSecondary: string; - textPrimary: string; - textBright: string; - accent: string; - accentHover: string; - tabBg: string; // inactive SSH tab background - tabActiveBg: string; - tabActiveText: string; - tabActiveBorder: string; - vaultTabBg: string; // vault/home tab — inactive background - vaultTabActiveBg: string; // vault/home tab — active background - statusConnected: string; - statusError: string; - statusConnecting: string; - statusWarning: string; - textNotice: string; // notice/info boxes text and icon -} - -export interface AppTheme { - id: string; - name: string; - builtIn: boolean; - uiFontFamily: string; - uiFontSize: number; - terminalFontFamily: string; - terminalFontSize: number; - ui: UITheme; - terminal: TerminalTheme; -} - -export type Locale = "en" | "fr" | "ru" | "zh"; - - -// ─── Types exposés aux plugins ───────────────────────────────────────────── - -export interface PluginConnection { - id: string; - name?: string; - host: string; - port: number; - username: string; - auth_type: "password" | "key"; - tags: string[]; - identity_id?: string; - jump_hosts?: JumpHost[]; - // Display-only fields — already present at runtime (runtime.ts:389 returns - // full Connection records cast to PluginConnection[]); exposed here so the - // agent UI can render a real per-host avatar. Optional and additive. - connection_type?: "ssh" | "serial" | "ftp"; - icon?: string; - distro?: string; - serial_port?: string; -} - -export interface PluginConnectionInput { - name?: string; - host: string; - port: number; - username: string; - auth_type: "password" | "key"; - tags?: string[]; - identity_id?: string; - jump_hosts?: JumpHost[]; -} - -export interface PluginKey { - id: string; - name?: string; - key_type?: string; - tags: string[]; -} - -export interface PluginIdentity { - id: string; - name?: string; - username: string; - key_id?: string; - tags: string[]; -} - -export interface OmniCommand { - id: string; - label: string; - icon: string; - keywords?: string[]; - section?: string; - /** Optional keyboard shortcut. Format: "ctrl+k", "meta+shift+p". First-registered wins on conflict. */ - keybinding?: string; - /** ID of a core shortcut to resolve as the hint (reactive, updates when user rebinds). */ - shortcutId?: string; - execute: () => void | Promise; -} - -/** A label that may be a function, re-resolved by the host on locale change. - * A plain string is frozen at registration time. */ -export type PluginLabel = string | (() => string); - -export interface SettingsPage { - id: string; - label: PluginLabel; - icon: string; - component: React.FC; -} - -export interface RightPanelSection { - id: string; - label: PluginLabel; - icon: string; - component: React.FC; - /** Opt-in: this section drives the terminal status bar's high-CPU indicator - * and its metrics stream. Explicit flag rather than an id check, so a plugin - * can't inherit the host integration by squatting another plugin's section id. */ - providesHostMetrics?: boolean; - /** Opt-in: this section owns an in-panel search bar that Ctrl+F should focus - * when the section is open, via the "voltius:focus-panel-search" event. */ - providesPanelSearch?: boolean; - /** Rail position, ascending. Sections without one sort last; ties break on `id`. - * Registration order is NOT stable — it follows the on-disk read order of the - * seeded plugin directory and changes after any uninstall/reinstall — so a - * section that wants a fixed rail slot must declare it here. */ - order?: number; -} - -/** Nav-stack entries a plugin may push via `pushMobileScreen`. Each member's - * `kind` must exist as a "panel-" variant of mobileNavCore's MobileScreen - * union — runtime.ts's translator switch is exhaustively checked against that - * union, so adding a member here without a matching host variant is a type - * error, not a silent no-op at runtime. */ -export type PluginMobileNavEntry = { - kind: "docker-logs"; - sessionId: string; - containerId: string; - containerName: string; -}; - -/** Props the host passes into a registered mobile screen's `render`. Extra - * navigation params (e.g. docker-logs' containerId/containerName) ride along - * as additional keys — see `pushMobileScreen`. */ -export interface MobileScreenProps { - sessionId: string; - /** Pop this screen off the mobile nav stack. MobilePanelHeader itself can't - * cross the plugin boundary (it reaches into the host's nav store), so the - * screen must render its own header chrome and wire this to its back button. */ - onBack: () => void; - [key: string]: unknown; -} - -export interface MobileScreen { - id: string; - /** Screen key MobileShell looks up on navigation, e.g. "docker", "metrics". */ - kind: string; - render: React.FC; -} - -export interface GlobalPanel { - id: string; - /** Rendered at shell level (not session-scoped). Host drives open/close. */ - component: React.FC<{ open: boolean; onClose: () => void }>; -} - -export interface PluginSession { - id: string; - connectionId: string; - connectionName: string; - status: string; - type: string; - /** Local sessions only: the shell path/name to use for a spawned exec PTY. */ - localShell?: string; -} - -export type PluginTheme = AppTheme; - -// ─── Notification types ──────────────────────────────────────────────────── - -export type ToastSeverity = 'info' | 'success' | 'warning' | 'error'; - -export interface ToastOptions { - severity?: ToastSeverity; - duration?: number; - action?: { label: string; onClick: () => void }; -} - -export interface ProgressOptions { - indeterminate?: boolean; - cancellable?: boolean; -} - -export interface ProgressHandle { - update(value: number, message?: string): void; - finish(message?: string): void; - error(message: string): void; - cancel(): void; -} - -export interface BannerOptions { - severity?: ToastSeverity; - actions?: Array<{ label: string; onClick: () => void }>; - dismissable?: boolean; - flashToast?: boolean; -} - -export interface BannerHandle { - dismiss(): void; - update(message: string): void; -} - -// ─── UI Contribution types ───────────────────────────────────────────────── - -/** A single action item contributed by a plugin to a UI slot. */ -export interface ContributedAction { - label: string; - icon?: string; - onClick: () => void; - divider?: boolean; - danger?: boolean; - /** Keyboard shortcut hint displayed on the right in context menus */ - shortcut?: string; - /** If provided, item is only shown when this returns true. Errors are treated as false. */ - when?: (context: unknown) => boolean; -} - -/** Named UI slots where plugins can inject actions. */ -export type UISlot = - | "connection.contextMenu" - | "connection.panelActions" - | "key.contextMenu" - | "key.panelActions" - | "identity.contextMenu" - | "identity.panelActions" - | "portForwardingRule.contextMenu" - | "home.bgContextMenu" - | "keychain.bgContextMenu" - | "home.toolbar.hostMenu" - | "settings.vaults"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type UIContributionFactory = (ctx: any) => ContributedAction[]; - -export type UIStatusBarSlot = "terminal.statusBar.right" | "titlebar.right"; - -export interface TerminalStatusBarContributionContext { - sessionId: string; - sessionType: "ssh" | "local" | "serial"; - connectionId: string; - connectionName?: string; - sessionStatus: "connecting" | "connected" | "disconnected" | "error"; - connection?: PluginConnection; - serialConfig?: SerialConnectParams; - dimensions?: { cols: number; rows: number }; -} - -export type UIStatusBarContributionFactory = (ctx: TerminalStatusBarContributionContext) => ReactNode; - -export type StreamKind = "metrics" | "processes" | "docker-logs" | "docker-stack-logs"; - -export interface StreamsAPI { - /** Start a session-scoped stream. Returns a streamId. */ - start(kind: StreamKind, opts: Record): Promise; - /** Stop a stream. No-op for an unknown id. */ - stop(streamId: string): Promise; - /** Subscribe to a started stream's snapshots. Resolves to an unsubscribe fn. */ - on(streamId: string, cb: (snapshot: T) => void): Promise<() => void>; -} - -/** Host metrics — built on top of api.streams' "metrics" kind. GATED (metrics:read). */ -export interface MetricsAPI { - start(sessionId: string, isRemote: boolean): Promise; - stop(streamId: string): Promise; - onSnapshot(streamId: string, cb: (snapshot: T) => void): Promise<() => void>; - getSystemInfo(sessionId: string, sessionType: string, sessionName?: string): Promise; -} - -/** Process listing/kill — built on top of api.streams' "processes" kind. GATED, split - * two ways: processes:read covers start/onSnapshot/stop; processes:manage covers kill. */ -export interface ProcessesAPI { - start(sessionId: string, isRemote: boolean): Promise; - stop(streamId: string): Promise; - onSnapshot(streamId: string, cb: (snapshot: T) => void): Promise<() => void>; - kill(sessionId: string, pid: number, isRemote: boolean, force: boolean): Promise; -} - -/** Not gated — a pure KDF over caller-supplied input, grants no access to host secrets. */ -export interface CryptoAPI { - /** Derive a 32-byte key from a passphrase and hex salt. Returns hex. */ - deriveKey(passphrase: string, saltHex: string): Promise; -} - -/** Locales the host ships. A plugin's catalog may cover any subset — "en" should - * always be present, since it is the fallback when the active locale is missing. - * Re-exports the host's own `Locale` union (type-only, erased at build — this - * doesn't pull `@/stores/localeStore` into the plugin bundle) so a future host - * locale addition flows through here automatically instead of drifting out of sync. */ -export type PluginLocale = Locale; - -/** A flat key → template map for one locale. Values may contain "{{var}}" placeholders. */ -export type PluginLocaleCatalog = Record; - -export type PluginI18nCatalog = Partial>; - -/** - * Not gated — reading/resolving UI strings grants no host access. Each plugin owns - * its own catalog (registered here, not in the host's locale files) so a third-party - * plugin can ship translations exactly the way a first-party one does. - */ -export interface I18nAPI { - /** Register (or replace) this plugin's translation catalog. Call once at load, - * before rendering anything that resolves keys. */ - register(catalog: PluginI18nCatalog): void; - /** Resolve `key` against the host's active locale. Falls back to the "en" entry, - * then to `key` itself (visible, never blank) if neither has it. */ - t(key: string, vars?: Record): string; - /** The host's current active locale. */ - getLocale(): PluginLocale; - /** Fires whenever the host's active locale changes. Re-call `t()` and re-render - * on each firing — this does not itself trigger a React re-render. Returns an - * unsubscribe function. */ - onLocaleChange(cb: (locale: PluginLocale) => void): () => void; -} - -/** Proxmox VE LXC management. GATED, split two ways: proxmox:read covers - * list/snapshots.list; proxmox:manage covers everything else. Only functions - * against SSH sessions. */ -export interface ProxmoxAPI { - lxc: { - list(sessionId: string): Promise; - action(sessionId: string, vmid: number, action: string): Promise; - /** Opens a pct-exec shell into the container and returns the new session's id. - * vmName is display-only — used for the resulting terminal tab's label. */ - openShell(sessionId: string, vmid: number, vmName?: string): Promise; - snapshots: { - list(sessionId: string, vmid: number): Promise; - create(sessionId: string, vmid: number, name: string, description?: string): Promise; - rollback(sessionId: string, vmid: number, name: string): Promise; - remove(sessionId: string, vmid: number, name: string): Promise; - }; - }; -} - -/** Where a docker command runs. Replaces the repeated (sessionId, isRemote, localShell) - * triple the underlying commands take — a transposed boolean in a positional call is - * silent; this shape makes every call site self-describing and tsc-checked. */ -export interface DockerTarget { - sessionId: string; - isRemote: boolean; - localShell: string | null; -} - -/** - * Docker container/image/volume/network/stack management. GATED, split two ways - * (kipavy ruling): docker:read covers every list/services/checkUpdate verb and all - * of logs.*; docker:manage covers everything that mutates or destroys, including - * exec.open (an interactive shell inside a container is full control, not a read). - */ -export interface DockerAPI { - containers: { - list(t: DockerTarget): Promise; - action(t: DockerTarget, containerId: string, action: string): Promise; - /** Reconstructs the `docker run` command for the container. `command` is the - * container's image ref, passed through to the backend command as `image`. */ - runCommand(t: DockerTarget, containerId: string, command: string): Promise; - }; - images: { - list(t: DockerTarget): Promise; - remove(t: DockerTarget, imageId: string): Promise; - pull(t: DockerTarget, image: string): Promise; - checkUpdate(t: DockerTarget, imageId: string): Promise; - /** Pulls `image` and, when `recreate` is set, recreates the containers using it. */ - update(t: DockerTarget, imageId: string, recreate: boolean): Promise; - recreateContainers(t: DockerTarget, imageId: string): Promise; - prune(t: DockerTarget): Promise; - }; - volumes: { - list(t: DockerTarget): Promise; - remove(t: DockerTarget, name: string): Promise; - prune(t: DockerTarget): Promise; - }; - networks: { - list(t: DockerTarget): Promise; - remove(t: DockerTarget, id: string): Promise; - prune(t: DockerTarget): Promise; - }; - stacks: { - list(t: DockerTarget): Promise; - services(t: DockerTarget, stack: string): Promise; - action(t: DockerTarget, stack: string, action: string): Promise; - update(t: DockerTarget, stack: string): Promise; - }; - logs: { - start(t: DockerTarget, containerId: string, tail: number): Promise; - startStack(t: DockerTarget, stack: string, tail: number): Promise; - stop(streamId: string): Promise; - /** Payload is a DockerLogLine ({ line, stream }), not a bare string — kept generic like StreamsAPI.on. */ - on(streamId: string, cb: (payload: T) => void): Promise<() => void>; - }; - system: { prune(t: DockerTarget): Promise }; - exec: { - /** Opens an interactive shell into the container and returns the new session's id. - * containerName is display-only — used for the resulting terminal tab's label. */ - open(t: DockerTarget, containerId: string, containerName?: string): Promise; - }; -} - -// ─── API principale ──────────────────────────────────────────────────────── - -export interface PluginAPI { - pluginId: string; - /** Returns true if this plugin is currently enabled in the registry. */ - isActive(): boolean; - - // SSH keys (requires keys:*) - keys: { - list(): Promise; - /** Creates a key entry and stores private/public content in the vault. */ - create(data: { name?: string; key_type?: string; tags?: string[] }, privateKey: string, publicKey?: string): Promise; - delete(id: string): Promise; - }; - - // Identities (requires identities:*) - identities: { - list(): Promise; - create(data: { name?: string; username: string; key_id?: string; tags?: string[] }): Promise; - delete(id: string): Promise; - }; - - // Connections (requires connections:*) - connections: { - list(): Promise; - get(id: string): Promise; - create(data: PluginConnectionInput): Promise; - update(id: string, data: Partial): Promise; - delete(id: string): Promise; - bulkImport(items: PluginConnectionInput[]): Promise; - subscribe(cb: (connections: PluginConnection[]) => void): () => void; - }; - - // Vault — plugin-scoped secrets (requires vault:*) - vault: { - get(key: string): Promise; - set(key: string, value: string): Promise; - delete(key: string): Promise; - }; - - // Themes (requires "themes") - themes: { - register(theme: PluginTheme): void; - unregister(id: string): void; - }; - - // OmniSearch (requires "omni-commands") - omni: { - register(command: OmniCommand): () => void; - unregister(id: string): void; - }; - - // UI — extension points - ui: { - registerSettingsPage(page: SettingsPage): () => void; - registerRightPanelSection(section: RightPanelSection): () => void; - /** Mount a global, shell-level panel (not session-scoped). Returns cleanup. */ - registerGlobalPanel(panel: GlobalPanel): () => void; - /** Contribute a full-screen mobile view for `screen.kind`. Uninstalling or - * disabling the plugin removes it, same as registerRightPanelSection does - * on desktop. Returns cleanup. */ - registerMobileScreen(screen: MobileScreen): () => void; - /** Push another mobile screen onto the nav stack (e.g. docker's container - * list pushing its logs view). Writes to the mobile nav store regardless - * of platform — harmless on desktop, since MobileShell is never mounted - * there. */ - pushMobileScreen(entry: PluginMobileNavEntry): void; - /** Switch the mobile shell to its terminal tab — e.g. after opening an exec - * shell. Writes to the mobile nav store regardless of platform — harmless - * on desktop, since MobileShell is never mounted there. */ - focusMobileTerminal(): void; - /** Inject action items into a named UI slot. Returns a cleanup function. */ - registerContribution(slot: UISlot, fn: (ctx: C) => ContributedAction[]): () => void; - /** Render a React widget in the terminal status bar's right-side slot. Returns a cleanup function. */ - registerStatusBarItem(slot: UIStatusBarSlot, fn: UIStatusBarContributionFactory): () => void; - unregister(id: string): void; - /** Switch the app's active navigation section. */ - setActiveNav(id: string): void; - /** Publish a plain, serialisable state snapshot for host UI to read, keyed - * by `::`. Host surfaces subscribe to this instead of - * importing the plugin's runtime module. Cleared on unload/disable. */ - publishState(key: string, value: unknown): void; - }; - - // Plugin-scoped key-value storage - storage: { - get(key: string): Promise; - set(key: string, value: T): Promise; - delete(key: string): Promise; - }; - - // HTTP (requiert "http") - http: { - get(url: string, opts?: RequestInit): Promise; - post(url: string, body: unknown, opts?: RequestInit): Promise; - /** Streaming request. Returns a Response with a ReadableStream body (for SSE/LLM streaming). */ - stream(url: string, init?: RequestInit): Promise; - }; - - // Filesystem restricted to home (requires "fs") - fs: { - readText(path: string): Promise; - writeText(path: string, content: string): Promise; - exists(path: string): Promise; - /** Polling-based file watch. Calls cb when content changes. Returns cleanup fn. */ - watch(path: string, cb: () => void, opts?: { intervalMs?: number }): () => void; - }; - - // Event bus (always available) - events: { - on(event: string, handler: (data: unknown) => void): () => void; - emit(event: string, data?: unknown): void; - }; - - // Notifications (requires "notifications") - notifications: { - toast(message: string, opts?: ToastOptions): void; - progress(title: string, opts?: ProgressOptions): ProgressHandle; - banner(message: string, opts?: BannerOptions): BannerHandle; - }; - - // Plugin-scoped logger - log: { - info(msg: string, ...args: unknown[]): void; - warn(msg: string, ...args: unknown[]): void; - error(msg: string, ...args: unknown[]): void; - }; - - // Sessions (requires sessions:read / sessions:write) - sessions: { - /** Returns current sessions snapshot. */ - list(): PluginSession[]; - /** The session backing the active terminal tab, or null if there is none. */ - getActive(): PluginSession | null; - /** Fires when a session becomes connected. */ - onConnected(cb: (session: PluginSession) => void): () => void; - /** Fires when a connected session is removed or disconnected. */ - onDisconnected(cb: (session: PluginSession) => void): () => void; - /** Fires when the user switches to a different terminal tab. */ - onActivated(cb: (session: PluginSession) => void): () => void; - /** Send a command to a session. Runtime appends \n. Requires sessions:write. */ - sendCommand(sessionId: string, cmd: string): Promise; - /** Open (connect) a saved connection by id. Resolves to the new sessionId. Requires sessions:write. */ - open(connectionId: string): Promise; - /** Close (disconnect) a session by id. Requires sessions:write. */ - close(sessionId: string): Promise; - }; - - // Terminal output — GATED (first-party only). Requires terminal:read / terminal:stream. - terminal: { - /** Last `maxLines` lines of a session's buffer as text (default 200). */ - readSnapshot(sessionId: string, maxLines?: number): string; - /** The session's current selection as text, or "" if nothing is selected. */ - readSelection(sessionId: string): string; - /** Subscribe to live decoded output for a session. Resolves to an unsubscribe fn. */ - onOutput(sessionId: string, cb: (text: string) => void): Promise<() => void>; - }; - - // Keychain — GATED (first-party only). OS-local, never synced. - // Requires keychain:read / keychain:write. - keychain: { - /** Read a value from the OS keychain. Returns null if unset. */ - get(key: string): Promise; - /** Write a value to the OS keychain. */ - set(key: string, value: string): Promise; - /** Delete a value from the OS keychain (no-op if absent). */ - delete(key: string): Promise; - }; - - // Session-scoped streams (metrics, processes, docker logs) — GATED per kind. - streams: StreamsAPI; - - // Host metrics domain wrapper over streams — GATED (metrics:read). - metrics: MetricsAPI; - - // Process listing/kill domain wrapper over streams — GATED, split - // processes:read (start/onSnapshot/stop) / processes:manage (kill). - processes: ProcessesAPI; - - // Key derivation (requires crypto:derive). Not gated — pure KDF over caller input. - crypto: CryptoAPI; - - // Plugin-owned UI translation catalog (requires "ui"). Not gated. - i18n: I18nAPI; - - // Proxmox VE LXC management — GATED, split - // proxmox:read (list/snapshots.list) / proxmox:manage (everything else). - proxmox: ProxmoxAPI; - - // Docker container/image/volume/network/stack management — GATED, split - // docker:read (list/services/checkUpdate/logs.*) / docker:manage (everything else). - docker: DockerAPI; - - // Lifecycle hooks (always available) - lifecycle: { - /** Fires when an SSH/local session transitions to "connected". */ - onConnectionEstablished(cb: (conn: PluginConnection) => void): () => void; - /** Fires when a connected session is removed or becomes disconnected. */ - onConnectionClosed(cb: (conn: PluginConnection) => void): () => void; - /** Fires when the user switches to a different terminal tab. */ - onSessionActivated(cb: (session: PluginSession) => void): () => void; - /** Fires when this plugin's own storage.set() is called. */ - onSettingsChanged(cb: (key: string, value: unknown) => void): () => void; - /** Fires before the app closes. Must resolve within 5 seconds. */ - onBeforeQuit(cb: () => void | Promise): () => void; - /** Resolves once the login-time server sync has completed (or immediately for local/offline users). */ - waitForLoginSync(): Promise; - }; - - // Sync / blob storage (requires sync:read / sync:write) - sync: { - /** Read a plugin-scoped blob from local storage. Returns null if not set. */ - getBlob(key: string): Promise; - /** Write a plugin-scoped blob to local storage. Max 1 MB. */ - setBlob(key: string, data: Uint8Array): Promise; - /** - * Register a callback that fires after a sync completes and the stored - * blob for `key` has changed. Note: cross-device sync of plugin blobs - * requires future Tauri backend support — currently fires on local changes only. - */ - onRemoteChange(key: string, cb: (data: Uint8Array) => void): () => void; - /** Reload a named in-app store (e.g. "connections", "identities", "keys"). */ - triggerReload(storeKey: string): Promise; - /** - * Export the full app state (connections, keys, identities, secrets) as a - * base64-encoded XChaCha20-Poly1305 encrypted blob — same format as cloud sync. - * encKey: 64-char hex string (32 bytes). Requires sync:write. - */ - exportState(encKey: string, deviceId: string): Promise; - /** - * CRDT-merge one or more remote encrypted blobs into local state, then - * reload all entity stores. blobs: base64-encoded (same format as exportState). - * Requires sync:write. - */ - importStates(encKey: string, blobs: string[]): Promise; - }; - - // Inter-plugin communication (always available) - plugins: { - /** Publish this plugin's public API surface so other plugins can consume it. */ - expose(publicApi: unknown): void; - /** Get another plugin's exposed API. Returns null if not loaded or not exposed. */ - getApi(pluginId: string): unknown | null; - }; -} - -export type PluginRegisterFn = (api: PluginAPI) => (() => void) | void; - -// ─── Settings schema ─────────────────────────────────────────────────────── - -export interface PluginConfigField { - type: "string" | "number" | "boolean" | "select"; - default: unknown; - description: string; - /** Overrides the auto-derived label (the host humanizes the key by default). */ - label?: string; - options?: string[]; // for select - secret?: boolean; // render as password input - min?: number; // for number: minimum (also clamps on save) - max?: number; // for number: maximum (also clamps on save) -} - -export interface PluginManifest { - id: string; - name: string; - version: string; - /** Minimum app version required to run this plugin. Falls back to the app version - * at build time when the manifest omits it. */ - minAppVersion?: string; - description?: string; - permissions: string[]; - defaultEnabled?: boolean; - /** Hidden in the plugin list on mobile (uses host-only resources, e.g. local fs). */ - desktopOnly?: boolean; - contributes?: { - configuration?: Record; - }; -} - diff --git a/types/voltius-ui.d.ts b/types/voltius-ui.d.ts deleted file mode 100644 index 145ccf0..0000000 --- a/types/voltius-ui.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Components the host exposes to plugins. Externalized at build time so you use -// the host's own instances — see the build script in package.json. -declare module "@voltius/ui" { - import type { ComponentType, ReactNode } from "react"; - export const Icon: ComponentType<{ icon: string; width?: number | string; className?: string }>; - export const InfoTooltip: ComponentType<{ text: string; children?: ReactNode }>; - export const BottomSheet: ComponentType<{ title?: string; onClose: () => void; children?: ReactNode }>; - export function useAutosave(value: T, save: (v: T) => void | Promise, delayMs?: number): void; -}