@termix-ssh/plugin-sdk 1.0.0 → 1.0.2

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +8 -1
  3. package/cli/commands/changelog.mjs +25 -0
  4. package/cli/commands/dev.mjs +158 -0
  5. package/cli/commands/validate.mjs +16 -24
  6. package/cli/index.mjs +8 -0
  7. package/cli/lib/tmxplug.mjs +1 -1
  8. package/dist/backend.d.ts +6 -2
  9. package/dist/changelog.d.ts +62 -0
  10. package/dist/changelog.js +187 -0
  11. package/dist/frontend.d.ts +67 -1
  12. package/dist/host/{chunk-F3SDZDDG.js → chunk-LKYAVM4T.js} +17 -2
  13. package/dist/host/{chunk-XH5NXL6Y.js → chunk-SS43S6VI.js} +7816 -7344
  14. package/dist/host/{chunk-XGWE7BTJ.js → chunk-XKMRKQFN.js} +6 -1
  15. package/dist/host/{linked-server-FZGWQW32.js → linked-server-DW5P3NLZ.js} +1 -1
  16. package/dist/host/{rbac-api-6KDMBVDZ.js → rbac-api-JKISXZEP.js} +1 -1
  17. package/dist/host/testing-host.js +38 -5
  18. package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +12 -1
  19. package/dist/host/types/src/types/host-sidebar-preferences.d.ts +18 -1
  20. package/dist/host/types/src/types/onboarding.d.ts +29 -0
  21. package/dist/host/types/src/types/plugin-onboarding.d.ts +39 -0
  22. package/dist/host/types/src/types/ui-preferences.d.ts +13 -4
  23. package/dist/host/types/src/types/ui-types.d.ts +1 -1
  24. package/dist/host/types/src/ui/api/plugins-api.d.ts +211 -0
  25. package/dist/host/types/src/ui/api/system-status-api.d.ts +24 -0
  26. package/dist/host/types/src/ui/components/connection/ConnectionLogPanel.d.ts +2 -1
  27. package/dist/host/types/src/ui/components/connection/ConnectionScreen.d.ts +5 -5
  28. package/dist/host/types/src/ui/components/list-kit.d.ts +102 -0
  29. package/dist/host/types/src/ui/components/panel-layout.d.ts +6 -1
  30. package/dist/host/types/src/ui/components/surface/surface-scope.d.ts +5 -1
  31. package/dist/host/types/src/ui/contexts/UiPreferencesContext.d.ts +15 -2
  32. package/dist/host/types/src/ui/hooks/use-installed-plugins.d.ts +6 -0
  33. package/dist/host/types/src/ui/hooks/use-permissions.d.ts +2 -0
  34. package/dist/host/types/src/ui/lib/embedded-frame.d.ts +14 -0
  35. package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +49 -0
  36. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
  37. package/dist/host/types/src/ui/plugins/open-plugins.d.ts +5 -0
  38. package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +10 -0
  39. package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +2 -0
  40. package/dist/host/types/src/ui/sidebar/rail-items.d.ts +10 -0
  41. package/dist/host/ui.js +1097 -787
  42. package/dist/host/{use-permissions-NULZL43J.js → use-permissions-5CZEA3P4.js} +3 -1
  43. package/dist/manifest.d.ts +19 -2
  44. package/dist/manifest.js +82 -1
  45. package/dist/testing/setup.js +8 -0
  46. package/dist/testing.d.ts +12 -0
  47. package/dist/vitest-preset.d.ts +3 -0
  48. package/dist/vitest-preset.js +6 -0
  49. package/package.json +7 -3
  50. package/schema/manifest.schema.json +12 -1
  51. package/cli/lib/changelog.mjs +0 -126
  52. package/schema/changelog.schema.json +0 -67
@@ -21,7 +21,7 @@ var inFlight = null;
21
21
  function load() {
22
22
  if (cache) return Promise.resolve(cache);
23
23
  if (inFlight) return inFlight;
24
- inFlight = import("./rbac-api-6KDMBVDZ.js").then(({ getMyPermissions }) => getMyPermissions()).then((result) => {
24
+ inFlight = import("./rbac-api-JKISXZEP.js").then(({ getMyPermissions }) => getMyPermissions()).then((result) => {
25
25
  cache = {
26
26
  permissions: result.permissions ?? [],
27
27
  isAdmin: !!result.isAdmin
@@ -39,6 +39,10 @@ async function hasPermission(permission) {
39
39
  const { permissions, isAdmin } = await load();
40
40
  return matchesPermission(permissions, isAdmin, permission);
41
41
  }
42
+ async function loadPermissionChecker() {
43
+ const { permissions, isAdmin } = await load();
44
+ return (permission) => matchesPermission(permissions, isAdmin, permission);
45
+ }
42
46
  function preloadPermissions() {
43
47
  return load().then(() => void 0);
44
48
  }
@@ -82,6 +86,7 @@ function setPermissionsForTesting(permissions, isAdmin = false) {
82
86
 
83
87
  export {
84
88
  hasPermission,
89
+ loadPermissionChecker,
85
90
  preloadPermissions,
86
91
  notifyPermissionsChanged,
87
92
  usePermissions,
@@ -3,7 +3,7 @@ import {
3
3
  SYNC_CHANGED_EVENT,
4
4
  getLinkedSession,
5
5
  notifySyncChanged
6
- } from "./chunk-F3SDZDDG.js";
6
+ } from "./chunk-LKYAVM4T.js";
7
7
  import "./chunk-YC2MAKSP.js";
8
8
  export {
9
9
  SYNC_CHANGED_EVENT,
@@ -23,7 +23,7 @@ import {
23
23
  shareHost,
24
24
  updateHostAccess,
25
25
  updateRole
26
- } from "./chunk-F3SDZDDG.js";
26
+ } from "./chunk-LKYAVM4T.js";
27
27
  import "./chunk-YC2MAKSP.js";
28
28
  export {
29
29
  assignRoleToUser,
@@ -32,6 +32,7 @@ import {
32
32
  listSettingsComponents,
33
33
  loadPaletteGroup,
34
34
  notifyShellReady,
35
+ onboardingSteps,
35
36
  registeredDashboardCardList,
36
37
  resetShellBridge,
37
38
  setPluginApiForTesting,
@@ -39,16 +40,16 @@ import {
39
40
  setShellCallbacks,
40
41
  setShellHosts,
41
42
  setShellLayoutProvider
42
- } from "./chunk-XH5NXL6Y.js";
43
+ } from "./chunk-SS43S6VI.js";
43
44
  import {
44
45
  setPermissionsForTesting
45
- } from "./chunk-XGWE7BTJ.js";
46
+ } from "./chunk-XKMRKQFN.js";
46
47
  import {
47
48
  getTabType,
48
49
  listTabTypes,
49
50
  setFrontendState,
50
51
  setPluginSummaries
51
- } from "./chunk-F3SDZDDG.js";
52
+ } from "./chunk-LKYAVM4T.js";
52
53
  import "./chunk-YC2MAKSP.js";
53
54
 
54
55
  // src/ui/plugin-host/testing-host.tsx
@@ -61,7 +62,8 @@ var CORE_UNSAVED = /* @__PURE__ */ new Set([
61
62
  "settings",
62
63
  "user-profile",
63
64
  "admin-settings",
64
- "split-screen"
65
+ "split-screen",
66
+ "plugins"
65
67
  ]);
66
68
  function isUnregisteredPluginTabType(type) {
67
69
  return !CORE_UNSAVED.has(type) && !getTabType(type);
@@ -191,6 +193,7 @@ async function renderPlugin(plugin, options = {}) {
191
193
  setFrontendState(pluginId, "active");
192
194
  if (options.ready) notifyShellReady();
193
195
  const mine = (items) => items.filter((item) => item.pluginId === pluginId);
196
+ const unprefixed = (items) => mine(items).map((item) => item.id.slice(pluginId.length + 1));
194
197
  const tabRecord = (type, host) => ({
195
198
  id: `${type}-test`,
196
199
  instanceId: `${type}-test`,
@@ -230,7 +233,8 @@ async function renderPlugin(plugin, options = {}) {
230
233
  secondFactors: () => mine(listSecondFactorUIs()).map((factor) => factor.id),
231
234
  paletteGroups: () => mine(listPaletteGroups()).map((group) => group.id),
232
235
  keybindingActions: () => mine(listKeybindingActions()).map((action) => action.id),
233
- keybindingDefaults: () => mine(listKeybindingDefaults()).map((binding) => binding.id)
236
+ keybindingDefaults: () => mine(listKeybindingDefaults()).map((binding) => binding.id),
237
+ onboardingSteps: () => unprefixed(onboardingSteps.list())
234
238
  },
235
239
  async loadPaletteGroup(id) {
236
240
  const group = listPaletteGroups().find((entry) => entry.id === id) ?? missing("a palette group", id);
@@ -303,6 +307,35 @@ async function renderPlugin(plugin, options = {}) {
303
307
  if (!Component) missing(`an extension "${component}" component`, id);
304
308
  return wrap(/* @__PURE__ */ jsx(Component, { ...props }));
305
309
  },
310
+ renderOnboardingStep(id, props = {}) {
311
+ const step = onboardingSteps.get(`${pluginId}:${id}`) ?? missing("an onboarding step", id);
312
+ const Component = step.Component;
313
+ let canContinue = true;
314
+ let beforeNext = null;
315
+ const element = wrap(
316
+ /* @__PURE__ */ jsx(
317
+ Component,
318
+ {
319
+ mode: "full",
320
+ isAdmin: true,
321
+ isDesktop: false,
322
+ shellReady: false,
323
+ setCanContinue: (ok) => {
324
+ canContinue = ok;
325
+ },
326
+ setBeforeNext: (fn) => {
327
+ beforeNext = fn;
328
+ },
329
+ ...props
330
+ }
331
+ )
332
+ );
333
+ return {
334
+ element,
335
+ canContinue: () => canContinue,
336
+ next: async () => beforeNext ? await beforeNext() : true
337
+ };
338
+ },
306
339
  renderDashboardCard(id) {
307
340
  const card = getRegisteredDashboardCard(id) ?? missing("a card", id);
308
341
  const Component = card.component;
@@ -19,7 +19,18 @@ export interface CredentialSidebarFilterState {
19
19
  type: string[];
20
20
  tags: string[];
21
21
  }
22
- export interface CredentialSidebarDisplayPreferences {
22
+ /** Which parts of a credential row are shown. */
23
+ export interface CredentialRowFields {
24
+ showUsername: boolean;
25
+ /** The "3h" count of hosts using it. */
26
+ showUsageCount: boolean;
27
+ showTypeBadge: boolean;
28
+ showPinIcon: boolean;
29
+ showSharedBadge: boolean;
30
+ }
31
+ export declare const CREDENTIAL_ROW_FIELD_KEYS: (keyof CredentialRowFields)[];
32
+ export declare function defaultCredentialRowFields(): CredentialRowFields;
33
+ export interface CredentialSidebarDisplayPreferences extends CredentialRowFields {
23
34
  density: CredentialDensity;
24
35
  showTags: boolean;
25
36
  trayTrigger: CredentialTrayTrigger;
@@ -29,7 +29,19 @@ export interface HostSidebarFilterState {
29
29
  features: string[];
30
30
  tags: string[];
31
31
  }
32
- export interface HostSidebarDisplayPreferences {
32
+ /** Which parts of a host row are shown. */
33
+ export interface HostRowFields {
34
+ showAddress: boolean;
35
+ showUsername: boolean;
36
+ showPort: boolean;
37
+ showPinIcon: boolean;
38
+ showSharedBadge: boolean;
39
+ /** Badges plugins add next to the name. */
40
+ showBadges: boolean;
41
+ }
42
+ export declare const HOST_ROW_FIELD_KEYS: (keyof HostRowFields)[];
43
+ export declare function defaultHostRowFields(): HostRowFields;
44
+ export interface HostSidebarDisplayPreferences extends HostRowFields {
33
45
  density: HostDensity;
34
46
  showTags: boolean;
35
47
  trayTrigger: HostTrayTrigger;
@@ -39,6 +51,11 @@ export interface HostSidebarDisplayPreferences {
39
51
  /** When false, nested folders hide the parent-path breadcrumb before their name. */
40
52
  showFolderPaths: boolean;
41
53
  hostClickBehavior: HostClickBehavior;
54
+ /**
55
+ * Host action id to whether it shows in the row's connect bar. An action
56
+ * missing here follows its own default. Every action stays in the menu.
57
+ */
58
+ barActions: Record<string, boolean>;
42
59
  }
43
60
  export interface HostSidebarPreferences {
44
61
  version: number;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Onboarding step bookkeeping, shared by the frontend and the ui-preferences
3
+ * route. Dependency-free for the backend's NodeNext build.
4
+ *
5
+ * Each step has a key and a version. A user's `seen` map records the highest
6
+ * version of each step they have been shown, so bumping one step's version
7
+ * shows that step again (and only that step) to everyone who saw it before.
8
+ * Plugin steps are keyed `<pluginId>:<id>`.
9
+ */
10
+ export declare const CORE_ONBOARDING_STEPS: {
11
+ readonly plugins: 1;
12
+ readonly "desktop-sync": 1;
13
+ readonly preset: 1;
14
+ readonly appearance: 1;
15
+ readonly security: 1;
16
+ };
17
+ export type CoreOnboardingStepKey = keyof typeof CORE_ONBOARDING_STEPS;
18
+ /**
19
+ * What a user who finished the old single-version onboarding counts as
20
+ * having seen. Frozen on purpose: a later bump in CORE_ONBOARDING_STEPS must
21
+ * still show the bumped step to these users.
22
+ */
23
+ export declare const LEGACY_SEEN: Readonly<Record<string, number>>;
24
+ export declare const MAX_SEEN_ENTRIES = 500;
25
+ export declare function pluginStepKey(pluginId: string, id: string): string;
26
+ export declare function isValidSeenEntry(key: string, version: unknown): boolean;
27
+ export declare function sanitizeSeen(input: unknown): Record<string, number>;
28
+ /** Per-key max, so a stale tab never lowers what another tab recorded. */
29
+ export declare function mergeSeen(a: Record<string, number> | undefined, b: Record<string, number> | undefined): Record<string, number>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The onboarding plugin picker: what the admin chose for each plugin, and the
3
+ * dependency rules that keep the result loadable. Shared by the picker (to
4
+ * show "kept because X needs it" live) and the backend apply route.
5
+ */
6
+ export type PluginChoice = "enabled" | "disabled" | "remove";
7
+ export declare const PLUGIN_CHOICES: readonly PluginChoice[];
8
+ export interface ChoicePlugin {
9
+ id: string;
10
+ /** Hard dependencies (plugin ids). */
11
+ dependencies: string[];
12
+ }
13
+ export interface ChoiceAdjustment {
14
+ id: string;
15
+ from: PluginChoice;
16
+ to: PluginChoice;
17
+ /** The plugins whose choice forced this one up. */
18
+ requiredBy: string[];
19
+ }
20
+ export declare function isPluginChoice(value: unknown): value is PluginChoice;
21
+ /**
22
+ * Raise choices until every plugin's dependencies can serve it: an enabled
23
+ * plugin needs its dependencies enabled, and a kept but disabled plugin
24
+ * needs them at least kept. Ids missing from `choices` stay as they are and
25
+ * are never adjusted.
26
+ */
27
+ export declare function resolvePluginChoices(plugins: ChoicePlugin[], choices: Record<string, PluginChoice>): {
28
+ choices: Record<string, PluginChoice>;
29
+ adjustments: ChoiceAdjustment[];
30
+ };
31
+ /**
32
+ * The order to apply resolved choices in: enable dependencies first, and
33
+ * disable or remove dependents first.
34
+ */
35
+ export declare function applyOrder(plugins: ChoicePlugin[], resolved: Record<string, PluginChoice>): {
36
+ enable: string[];
37
+ disable: string[];
38
+ remove: string[];
39
+ };
@@ -17,8 +17,6 @@
17
17
  */
18
18
  /** 2: the docker and host metrics areas moved to their plugins. */
19
19
  export declare const UI_PREFERENCES_VERSION = 3;
20
- /** Bump when onboarding gains steps existing users should be shown again. */
21
- export declare const UI_ONBOARDING_VERSION = 2;
22
20
  export type UiPreset = "simple" | "balanced" | "advanced" | "custom";
23
21
  export type UiAreaKey = "chrome" | "hostList" | "credentialList" | "rail" | "dashboard" | "hostEditor";
24
22
  export type UiDensity = "comfortable" | "compact";
@@ -50,6 +48,8 @@ export interface UiRailPreferences {
50
48
  * items are only known at runtime, so the preset cannot list them.
51
49
  */
52
50
  hidePluginItems?: boolean;
51
+ /** Rail item ids in the user's order. Items not listed keep their place. */
52
+ order?: string[];
53
53
  }
54
54
  export interface UiDashboardPreferences {
55
55
  enabledCards: string[];
@@ -73,10 +73,17 @@ export type UiOverrides = {
73
73
  [key: UiPluginAreaKey]: Record<string, unknown>;
74
74
  };
75
75
  export interface UiOnboardingState {
76
- /** 0 means "never completed". Compared against UI_ONBOARDING_VERSION. */
77
- completedVersion: number;
76
+ /** Step key to the highest version of that step the user was shown. */
77
+ seen: Record<string, number>;
78
+ /** When the first full run finished. Null means it never has. */
78
79
  completedAt: string | null;
79
80
  skipped: boolean;
81
+ /**
82
+ * Set for users carried over from the old single-version onboarding. The
83
+ * client marks every plugin step that exists right now as seen without
84
+ * showing it, then clears this.
85
+ */
86
+ baselinePending: boolean;
80
87
  }
81
88
  export interface UiPreferences {
82
89
  version: number;
@@ -87,6 +94,8 @@ export interface UiPreferences {
87
94
  export declare const PRESETS: Record<Exclude<UiPreset, "custom">, UiAreaPreferences>;
88
95
  export declare const UI_AREA_KEYS: UiAreaKey[];
89
96
  export declare function sanitizeUiOverrides(input: unknown): UiOverrides;
97
+ export declare function defaultOnboardingState(): UiOnboardingState;
98
+ export declare function sanitizeOnboarding(input: unknown): UiOnboardingState;
90
99
  export declare function defaultUiPreferences(): UiPreferences;
91
100
  export declare function sanitizeUiPreferences(input: unknown): UiPreferences;
92
101
  /**
@@ -128,7 +128,7 @@ export type HostFolder = {
128
128
  localOnly?: boolean;
129
129
  };
130
130
  /** Core's own tab types. Plugins register theirs at runtime. */
131
- type KnownTabType = "dashboard" | "host-manager" | "settings" | "user-profile" | "admin-settings" | "split-screen";
131
+ type KnownTabType = "dashboard" | "host-manager" | "settings" | "user-profile" | "admin-settings" | "split-screen" | "plugins";
132
132
  /**
133
133
  * TabType covers every built-in tab plus any plugin-contributed tab id.
134
134
  * `string & {}` (rather than plain `string`) keeps IDE autocomplete
@@ -1,3 +1,6 @@
1
+ import type { ChoiceAdjustment, PluginChoice } from "../../types/plugin-onboarding";
2
+ import type { ChangelogRelease, ReleaseNotes } from "@termix-ssh/plugin-sdk/changelog";
3
+ export type { PluginChoice };
1
4
  export interface PluginTabContribution {
2
5
  id: string;
3
6
  titleKey: string;
@@ -124,5 +127,213 @@ export interface PluginSummary {
124
127
  http: string[];
125
128
  ws: string[];
126
129
  };
130
+ registryId?: string | null;
131
+ autoUpdate?: boolean;
132
+ pinnedVersion?: string | null;
133
+ channel?: PluginChannel;
134
+ description?: string;
135
+ author?: string;
136
+ repository?: string;
137
+ /** A YouTube video id from the manifest. Admins only. */
138
+ videoId?: string;
139
+ /** What the plugin does, from the manifest. Admins only. */
140
+ features?: string[];
141
+ signedBy?: string | null;
127
142
  }
128
143
  export declare function getPlugins(): Promise<PluginSummary[]>;
144
+ /** Fired after any change to installed plugins; the loader resyncs on it. */
145
+ export declare const PLUGINS_CHANGED_EVENT = "termix:plugins-changed";
146
+ export type PluginChannel = "stable" | "beta";
147
+ export interface RegistryPluginVersion {
148
+ version: string;
149
+ /** A beta, offered only to plugins on the beta channel. */
150
+ prerelease?: boolean;
151
+ compatible: boolean;
152
+ capabilities: string[];
153
+ publishedAt?: string;
154
+ releaseNotesUrl?: string;
155
+ notes?: ReleaseNotes;
156
+ size: number;
157
+ }
158
+ export interface RegistryPluginEntry {
159
+ id: string;
160
+ name: string;
161
+ description: string;
162
+ author: string;
163
+ category: string;
164
+ repository?: string;
165
+ icon?: string;
166
+ videoId?: string;
167
+ features?: string[];
168
+ versions: RegistryPluginVersion[];
169
+ /** Newest release on the plugin's channel. */
170
+ latestVersion: string | null;
171
+ /** Newest beta, when one is newer than stable. */
172
+ latestBeta?: string | null;
173
+ channel?: PluginChannel;
174
+ installed: boolean;
175
+ installedVersion: string | null;
176
+ updateAvailable: boolean;
177
+ addedCapabilities: string[];
178
+ pinnedVersion: string | null;
179
+ autoUpdate: boolean;
180
+ bundled: boolean;
181
+ /** Active installs when known, release downloads otherwise. */
182
+ installCount: number | null;
183
+ installCountSource: string | null;
184
+ }
185
+ export interface RegistryListing {
186
+ registry: {
187
+ id: string;
188
+ url: string;
189
+ lastCheckedAt: string | null;
190
+ error: string | null;
191
+ };
192
+ managedByServer: boolean;
193
+ plugins: RegistryPluginEntry[];
194
+ }
195
+ export interface PluginStateChange {
196
+ id: string;
197
+ enabled: boolean;
198
+ enable: string[];
199
+ disable: string[];
200
+ missing: string[];
201
+ state?: string;
202
+ }
203
+ export interface PluginDataSummary {
204
+ id: string;
205
+ tables: Array<{
206
+ name: string;
207
+ rows: number | null;
208
+ }>;
209
+ kvKeys: number;
210
+ settings: {
211
+ admin: number;
212
+ user: number;
213
+ host: number;
214
+ secret: number;
215
+ };
216
+ migrations: Array<{
217
+ id: string;
218
+ appliedAt: string | null;
219
+ }>;
220
+ grants: Array<{
221
+ capability: string;
222
+ source: string;
223
+ grantedAt: string | null;
224
+ }>;
225
+ filesBytes: number;
226
+ }
227
+ export interface UpdateAllResult {
228
+ updated: Array<{
229
+ id: string;
230
+ version: string;
231
+ }>;
232
+ needsReview: Array<{
233
+ id: string;
234
+ capabilities: string[];
235
+ }>;
236
+ failed: Array<{
237
+ id: string;
238
+ error: string;
239
+ }>;
240
+ }
241
+ export declare function getPluginRegistry(refresh?: boolean): Promise<RegistryListing>;
242
+ /** `capabilities` is exactly what the consent prompt showed. */
243
+ export declare function installPlugin(pluginId: string, options: {
244
+ version?: string;
245
+ capabilities: string[];
246
+ }): Promise<{
247
+ id: string;
248
+ version: string;
249
+ state: string;
250
+ }>;
251
+ export declare function updatePlugin(pluginId: string, options?: {
252
+ version?: string;
253
+ acceptCapabilities?: boolean;
254
+ capabilities?: string[];
255
+ }): Promise<{
256
+ id: string;
257
+ version: string;
258
+ state: string;
259
+ }>;
260
+ export declare function updateAllPlugins(): Promise<UpdateAllResult>;
261
+ export declare function uninstallPlugin(pluginId: string): Promise<void>;
262
+ export declare function previewPluginState(pluginId: string, enabled: boolean): Promise<PluginStateChange>;
263
+ export declare function setPluginState(pluginId: string, enabled: boolean): Promise<PluginStateChange>;
264
+ export declare function retryPlugin(pluginId: string): Promise<void>;
265
+ export declare function setPluginOptions(pluginId: string, options: {
266
+ autoUpdate?: boolean;
267
+ pinned?: boolean;
268
+ channel?: PluginChannel;
269
+ }): Promise<{
270
+ autoUpdate: boolean;
271
+ pinnedVersion: string | null;
272
+ channel: PluginChannel;
273
+ }>;
274
+ export declare function setAllPluginChannels(channel: PluginChannel): Promise<{
275
+ changed: string[];
276
+ }>;
277
+ export declare function getPluginData(pluginId: string): Promise<PluginDataSummary>;
278
+ export declare function getPluginChangelog(pluginId: string): Promise<ChangelogRelease[]>;
279
+ export declare function deletePluginData(pluginId: string): Promise<void>;
280
+ export interface DeveloperModeState {
281
+ enabled: boolean;
282
+ /** TERMIX_REQUIRE_SIGNED_PLUGINS blocks file installs even in developer mode. */
283
+ signedOnly: boolean;
284
+ }
285
+ export declare function getDeveloperMode(): Promise<DeveloperModeState>;
286
+ /** Fired after developer mode changes; detail is the new value. */
287
+ export declare const DEVELOPER_MODE_CHANGED_EVENT = "termix:plugin-developer-mode";
288
+ export declare function setDeveloperMode(enabled: boolean): Promise<void>;
289
+ export interface PluginUploadPreview {
290
+ token: string;
291
+ id: string;
292
+ name: string;
293
+ version: string;
294
+ description: string;
295
+ author: string;
296
+ features?: string[];
297
+ capabilities: string[];
298
+ replaces: string | null;
299
+ }
300
+ export declare function uploadPlugin(file: Blob): Promise<PluginUploadPreview>;
301
+ export declare function installUploadedPlugin(token: string, capabilities: string[]): Promise<{
302
+ id: string;
303
+ version: string;
304
+ state: string;
305
+ }>;
306
+ export interface OnboardingPluginInfo {
307
+ id: string;
308
+ name: string;
309
+ description: string;
310
+ icon: string | null;
311
+ category: string;
312
+ version: string;
313
+ source: "bundled" | "user";
314
+ state: string;
315
+ dependencies: string[];
316
+ recommended: boolean;
317
+ consent: boolean;
318
+ }
319
+ export interface OnboardingPluginList {
320
+ pending: boolean;
321
+ /** On an upgrade the picker starts from what is on, not the defaults. */
322
+ reason: "fresh" | "upgrade" | null;
323
+ managedByLinkedServer: boolean;
324
+ plugins: OnboardingPluginInfo[];
325
+ }
326
+ export interface OnboardingApplyResult {
327
+ resolved: Record<string, PluginChoice>;
328
+ adjustments: ChoiceAdjustment[];
329
+ enabled: string[];
330
+ disabled: string[];
331
+ removed: string[];
332
+ failed: {
333
+ id: string;
334
+ error: string;
335
+ code?: string;
336
+ }[];
337
+ }
338
+ export declare function getOnboardingPlugins(): Promise<OnboardingPluginList>;
339
+ export declare function applyOnboardingPlugins(choices: Record<string, PluginChoice>): Promise<OnboardingApplyResult>;
@@ -29,6 +29,7 @@ export interface ReleasesRSSResponse {
29
29
  export declare function getReleasesRSS(perPage?: number): Promise<ReleasesRSSResponse>;
30
30
  export interface VersionInfo {
31
31
  status?: "up_to_date" | "requires_update" | "beta" | "unknown";
32
+ channel?: "stable" | "beta";
32
33
  /** Same value as remoteVersion; the endpoint sends both. */
33
34
  version?: string;
34
35
  localVersion?: string;
@@ -46,4 +47,27 @@ export interface VersionInfo {
46
47
  }
47
48
  export declare function releaseUrlFrom(info: VersionInfo | null | undefined): string;
48
49
  export declare function getVersionInfo(checkRemote?: boolean): Promise<VersionInfo>;
50
+ export type UpdateChannel = "stable" | "beta";
51
+ export interface UpdateChannelState {
52
+ channel: UpdateChannel;
53
+ /** The admin's choice; a beta build reports beta whatever it is. */
54
+ stored: UpdateChannel;
55
+ runningBeta: boolean;
56
+ }
57
+ export declare function getUpdateChannel(): Promise<UpdateChannelState>;
58
+ export declare function setUpdateChannel(channel: UpdateChannel): Promise<UpdateChannelState>;
59
+ export interface ReleaseInfo {
60
+ version: string;
61
+ tagName: string;
62
+ name: string;
63
+ url: string;
64
+ publishedAt: string;
65
+ prerelease: boolean;
66
+ notes: string;
67
+ }
68
+ export declare function getChannelReleases(): Promise<{
69
+ localVersion: string | null;
70
+ stable: ReleaseInfo | null;
71
+ beta: ReleaseInfo | null;
72
+ }>;
49
73
  export declare function getDatabaseHealth(): Promise<Record<string, unknown>>;
@@ -2,8 +2,9 @@ interface ConnectionLogPanelProps {
2
2
  isConnecting: boolean;
3
3
  isConnected: boolean;
4
4
  hasConnectionError: boolean;
5
+ /** Kept for older callers. The log always sits at the bottom. */
5
6
  position?: "top" | "bottom";
6
7
  className?: string;
7
8
  }
8
- export declare function ConnectionLogPanel({ isConnecting, isConnected, hasConnectionError, position, className, }: ConnectionLogPanelProps): import("react").JSX.Element;
9
+ export declare function ConnectionLogPanel({ isConnecting, isConnected, hasConnectionError, className, }: ConnectionLogPanelProps): import("react").JSX.Element;
9
10
  export {};
@@ -12,9 +12,7 @@ export interface ConnectionScreenProps {
12
12
  message?: string;
13
13
  /** Second line under the headline, usually user@host:port. */
14
14
  detail?: string;
15
- /** Headline once it has failed and is not retrying on its own. */
16
- errorMessage?: string;
17
- /** The reason it failed, shown under the headline. */
15
+ /** The reason it failed. Falls back to the last error in the log. */
18
16
  errorDetail?: string | null;
19
17
  /** Shown instead of a failure when the feature cannot run here. */
20
18
  unavailable?: ConnectionUnavailable | null;
@@ -23,12 +21,14 @@ export interface ConnectionScreenProps {
23
21
  maxAttempts?: number;
24
22
  nextRetryInMs?: number | null;
25
23
  onManualRetry?: () => void;
26
- retryLabel?: string;
24
+ /** Headline when it was up and dropped. */
27
25
  disconnectedMessage?: string;
26
+ /** Overrides closing the tab this sits in. False hides the button. */
27
+ onClose?: (() => void) | false;
28
28
  extraActions?: React.ReactNode;
29
29
  logPosition?: "top" | "bottom";
30
30
  emptyState?: React.ReactNode;
31
31
  className?: string;
32
32
  }
33
33
  /** The one screen every connection shows until it is up. */
34
- export declare function ConnectionScreen({ status, message, detail, errorMessage, errorDetail, unavailable, backgroundColor, attempt, maxAttempts, nextRetryInMs, onManualRetry, retryLabel, disconnectedMessage, extraActions, logPosition, emptyState, className, }: ConnectionScreenProps): React.JSX.Element;
34
+ export declare function ConnectionScreen({ status, message, detail, errorDetail, unavailable, backgroundColor, attempt, maxAttempts, nextRetryInMs, onManualRetry, disconnectedMessage, onClose, extraActions, logPosition, emptyState, className, }: ConnectionScreenProps): React.JSX.Element;