@medeo-plugin/sdk 0.7.0 → 0.9.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.
@@ -7,7 +7,7 @@ export type Permission = "storage" | "creation_open" | {
7
7
  "file_full_access": Array<string>;
8
8
  } | {
9
9
  "network": Array<string>;
10
- } | "agent_api" | "clipboard_read" | "clipboard_write" | "screen_capture" | "camera" | "microphone" | "system_audio" | "applications" | "global_shortcuts" | "notifications" | "tray" | "app_windows" | "accessibility" | "input_method" | {
10
+ } | "agent_api" | "clipboard_read" | "clipboard_write" | "screen_capture" | "camera" | "microphone" | "system_audio" | "applications" | "global_shortcuts" | "notifications" | "tray" | "app_windows" | "accessibility" | "window_management" | "input_method" | {
11
11
  "external_open": Array<string>;
12
12
  } | {
13
13
  "browser_view": Array<string>;
@@ -157,6 +157,28 @@ export type PackageSignature = {
157
157
  signature: string;
158
158
  index: string;
159
159
  };
160
+ export type About = {
161
+ /**
162
+ * Display name of the author, at most 80 characters. Official packages show Medeo.
163
+ */
164
+ author?: string;
165
+ /**
166
+ * SPDX license expression, at most 64 characters.
167
+ */
168
+ license?: string;
169
+ /**
170
+ * Project website.
171
+ */
172
+ homepage?: string;
173
+ /**
174
+ * Source repository.
175
+ */
176
+ repository?: string;
177
+ /**
178
+ * Where users report problems.
179
+ */
180
+ bugs?: string;
181
+ };
160
182
  export type PackageInfo = {
161
183
  manifest: Manifest;
162
184
  digest: string;
@@ -171,6 +193,11 @@ export type PackageInfo = {
171
193
  * shown at install and in the developer log, never a reason to refuse.
172
194
  */
173
195
  warnings?: Array<string>;
196
+ /**
197
+ * The package's signed `about.json` (author, license, links), when valid.
198
+ * Older Medeo ignore the file, so declaring it never blocks installation.
199
+ */
200
+ about?: About;
174
201
  };
175
202
  export type CapabilityDescriptor = {
176
203
  permission: string;
package/dist/index.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface PluginAPI {
13
13
  post(message: unknown): Promise<void>;
14
14
  onMessage(callback: (message: unknown) => void): () => void;
15
15
  invoke(method: MethodName, args?: Record<string, unknown>): Promise<unknown>;
16
- run(command: string): Promise<unknown>;
16
+ run(command: string, args?: JsonValue): Promise<unknown>;
17
17
  }
18
18
  declare global {
19
19
  var medeoPlugin: PluginAPI;
@@ -57,7 +57,7 @@ export declare const creation: {
57
57
  open: () => Promise<unknown>;
58
58
  };
59
59
  export declare const commands: {
60
- run: (command: string) => Promise<unknown>;
60
+ run: <T = unknown>(command: string, args?: JsonValue) => Promise<T>;
61
61
  };
62
62
  export interface Disposable {
63
63
  dispose(): unknown;
@@ -68,7 +68,7 @@ export interface PluginContext {
68
68
  subscriptions: Disposable[];
69
69
  /** Register a handler for each command the manifest declares; all must be registered when `activate` returns. */
70
70
  commands: {
71
- register(id: string, handler: () => unknown | Promise<unknown>): Disposable;
71
+ register(id: string, handler: (args?: JsonValue) => unknown | Promise<unknown>): Disposable;
72
72
  };
73
73
  /** `runtime: "node"` only: the installed, verified package directory (read-only). */
74
74
  extensionPath?: string;
@@ -193,7 +193,13 @@ export declare const shortcuts: {
193
193
  /** `panel: true` (macOS): a frameless window floating over every app and Space that does not
194
194
  * activate Medeo — the app the user was in keeps focus — opened at the pointer and hidden (not
195
195
  * closed) when it loses focus; opening it again moves it to the pointer. `hide` keeps a window
196
- * and its page alive for the next `open`/`focus`. */
196
+ * and its page alive for the next `open`/`focus`.
197
+ * `overlay: true` (macOS, needs `window_management` too): a transparent, shadowless panel covering
198
+ * the whole display under the pointer (menu bar included), above full-screen apps — for previews
199
+ * and radial menus. It takes keyboard focus without activating Medeo, hides when it loses focus,
200
+ * and each `open` moves it to the pointer's display; `width`/`height` are ignored. The page's
201
+ * `window.screenX/screenY` is the display's top-left in the same global points as
202
+ * `windowManagement`. `panel` and `overlay` are exclusive. */
197
203
  export declare const appWindows: {
198
204
  open: (options: {
199
205
  id: string;
@@ -202,6 +208,7 @@ export declare const appWindows: {
202
208
  width: number;
203
209
  height: number;
204
210
  panel?: boolean;
211
+ overlay?: boolean;
205
212
  }) => Promise<unknown>;
206
213
  close: (id: string) => Promise<unknown>;
207
214
  focus: (id: string) => Promise<unknown>;
@@ -435,6 +442,7 @@ export interface NetworkRequest {
435
442
  body?: string;
436
443
  body_base64?: string;
437
444
  timeout_ms?: number;
445
+ stream?: boolean;
438
446
  }
439
447
  export interface NetworkJob {
440
448
  id: string;
@@ -443,6 +451,7 @@ export interface NetworkJob {
443
451
  http_status?: number | null;
444
452
  headers?: [string, string][];
445
453
  error?: string | null;
454
+ stream?: boolean;
446
455
  }
447
456
  export interface NetworkChunk {
448
457
  data_base64: string;
@@ -454,6 +463,12 @@ export interface NetworkOrigin {
454
463
  declared: boolean;
455
464
  allowed: boolean;
456
465
  }
466
+ export interface NetworkServerEvent {
467
+ data: string;
468
+ event: string;
469
+ id: string;
470
+ retry?: number;
471
+ }
457
472
  export declare const network: {
458
473
  origins: () => Promise<NetworkOrigin[]>;
459
474
  requestOrigin: (origin: string, reason: string) => Promise<{
@@ -470,6 +485,14 @@ export declare const network: {
470
485
  }>;
471
486
  /** Resolves once the response has fully arrived; the job stays readable until released. */
472
487
  settled(id: string): Promise<NetworkJob>;
488
+ /** Single consumer for a stream:true job. Break/throw/abort cancels and releases it. */
489
+ bytes(id: string, signal?: AbortSignal): AsyncGenerator<Uint8Array>;
490
+ /** Incremental UTF-8 decoder, including code points split across HTTP chunks. */
491
+ textStream(id: string, signal?: AbortSignal): AsyncGenerator<string>;
492
+ /** SSE framing only: no automatic reconnect/retry or Last-Event-ID requests. */
493
+ sse(id: string, signal?: AbortSignal): AsyncGenerator<NetworkServerEvent>;
494
+ /** Explicitly dispose a stream that was never iterated (or has another owner). */
495
+ dispose(id: string): Promise<void>;
473
496
  text(id: string): Promise<string>;
474
497
  };
475
498
  /** Workspace operations use current directory references and asynchronous jobs. */
@@ -785,6 +808,61 @@ export declare const accessibility: {
785
808
  }>>;
786
809
  insertText: (focus: string, text: string) => Promise<NativeOperation<unknown>>;
787
810
  };
811
+ /** A rectangle in global points: origin at the top-left of the main display, y grows downwards
812
+ * (displays left of or above the main one have negative coordinates). */
813
+ export interface WindowFrame {
814
+ x: number;
815
+ y: number;
816
+ width: number;
817
+ height: number;
818
+ }
819
+ /** The frontmost window of another app. `id` stays the same for the same window while it lives. */
820
+ export interface ManagedWindow {
821
+ id: string;
822
+ title: string;
823
+ application: string;
824
+ bundle_id: string;
825
+ frame: WindowFrame;
826
+ resizable: boolean;
827
+ fullscreen: boolean;
828
+ minimized: boolean;
829
+ standard: boolean;
830
+ }
831
+ /** `visible_frame` excludes the menu bar and the Dock. `id` is the display number for this session,
832
+ * `uuid` survives restarts and reconnecting. The first entry is the main display. */
833
+ export interface ManagedScreen {
834
+ id: string;
835
+ uuid: string;
836
+ name: string;
837
+ primary: boolean;
838
+ scale: number;
839
+ frame: WindowFrame;
840
+ visible_frame: WindowFrame;
841
+ }
842
+ /** Window management (macOS, permission `window_management`, OS Accessibility). All calls are
843
+ * operations: `operations.wait(await windowManagement.focused())`. `request` opens the system
844
+ * prompt and is window-only; the others work in the worker too. `focused` never returns a Medeo window:
845
+ * when Medeo is in front (a plugin's own page) it is the window of the app right behind it, and `null`
846
+ * when there is none or the frontmost app has no window; `set_frame` reports the frame the app
847
+ * actually accepted (apps may clamp sizes) and fails with `resource_missing` once the window is gone. */
848
+ export declare const windowManagement: {
849
+ status: () => Promise<NativeOperation<{
850
+ authorization: "granted" | "denied";
851
+ }>>;
852
+ request: () => Promise<NativeOperation<{
853
+ authorization: "granted" | "denied";
854
+ }>>;
855
+ focused: () => Promise<NativeOperation<ManagedWindow | null>>;
856
+ setFrame: (id: string, frame: WindowFrame) => Promise<NativeOperation<{
857
+ frame: WindowFrame;
858
+ resized: boolean;
859
+ }>>;
860
+ screens: () => Promise<NativeOperation<ManagedScreen[]>>;
861
+ pointer: () => Promise<NativeOperation<{
862
+ x: number;
863
+ y: number;
864
+ }>>;
865
+ };
788
866
  export declare const browserView: {
789
867
  open: (id: string, url: string, width?: number, height?: number) => Promise<unknown>;
790
868
  state: (id: string) => Promise<unknown>;
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ export const storage = {
39
39
  set: (key, value) => globalThis.medeoPlugin.invoke('storage.set', { key, value }),
40
40
  };
41
41
  export const creation = { open: () => globalThis.medeoPlugin.invoke('creation.open') };
42
- export const commands = { run: (command) => globalThis.medeoPlugin.run(command) };
42
+ export const commands = { run: (command, args) => globalThis.medeoPlugin.run(command, args) };
43
43
  export function definePlugin(definition) {
44
44
  return { deactivate: () => undefined, ...definition };
45
45
  }
@@ -88,7 +88,13 @@ export const shortcuts = { list: () => globalThis.medeoPlugin.invoke('shortcuts.
88
88
  /** `panel: true` (macOS): a frameless window floating over every app and Space that does not
89
89
  * activate Medeo — the app the user was in keeps focus — opened at the pointer and hidden (not
90
90
  * closed) when it loses focus; opening it again moves it to the pointer. `hide` keeps a window
91
- * and its page alive for the next `open`/`focus`. */
91
+ * and its page alive for the next `open`/`focus`.
92
+ * `overlay: true` (macOS, needs `window_management` too): a transparent, shadowless panel covering
93
+ * the whole display under the pointer (menu bar included), above full-screen apps — for previews
94
+ * and radial menus. It takes keyboard focus without activating Medeo, hides when it loses focus,
95
+ * and each `open` moves it to the pointer's display; `width`/`height` are ignored. The page's
96
+ * `window.screenX/screenY` is the display's top-left in the same global points as
97
+ * `windowManagement`. `panel` and `overlay` are exclusive. */
92
98
  export const appWindows = { open: (options) => globalThis.medeoPlugin.invoke('windows.open', options), close: (id) => globalThis.medeoPlugin.invoke('windows.close', { id }), focus: (id) => globalThis.medeoPlugin.invoke('windows.focus', { id }), hide: (id) => globalThis.medeoPlugin.invoke('windows.hide', { id }), list: () => globalThis.medeoPlugin.invoke('windows.list') };
93
99
  export const tray = { set: (title, items, options = {}) => globalThis.medeoPlugin.invoke('tray.set', { title, items, ...options }), remove: () => globalThis.medeoPlugin.invoke('tray.remove'), events: () => globalThis.medeoPlugin.invoke('tray.events') };
94
100
  export const external = { open: (url) => globalThis.medeoPlugin.invoke('external.open', { url }) };
@@ -168,6 +174,121 @@ export const network = {
168
174
  await change.promise;
169
175
  change.off();
170
176
  } },
177
+ /** Single consumer for a stream:true job. Break/throw/abort cancels and releases it. */
178
+ async *bytes(id, signal) {
179
+ let offset = 0;
180
+ let abortCleanup;
181
+ const abort = () => { abortCleanup = network.dispose(id); void abortCleanup.catch(() => { }); };
182
+ signal?.addEventListener('abort', abort, { once: true });
183
+ try {
184
+ for (;;) {
185
+ let wake;
186
+ const changed = new Promise(resolve => { wake = resolve; });
187
+ const off = events.on(event => { if (event.type === 'operation' && event.id === id)
188
+ wake(); });
189
+ signal?.addEventListener('abort', wake, { once: true });
190
+ try {
191
+ if (signal?.aborted)
192
+ throw new Error('network_cancelled');
193
+ const state = await network.state(id);
194
+ if (state.status === 'failed' || state.status === 'cancelled')
195
+ throw new Error(state.error ?? state.status);
196
+ const part = await network.read(id, offset);
197
+ offset = part.next_offset;
198
+ if (signal?.aborted)
199
+ throw new Error('network_cancelled');
200
+ if (part.data_base64)
201
+ yield Uint8Array.from(atob(part.data_base64), c => c.charCodeAt(0));
202
+ if (part.eof)
203
+ return;
204
+ if (!part.data_base64)
205
+ await changed;
206
+ }
207
+ finally {
208
+ off();
209
+ signal?.removeEventListener('abort', wake);
210
+ }
211
+ }
212
+ }
213
+ finally {
214
+ signal?.removeEventListener('abort', abort);
215
+ await (abortCleanup ?? network.dispose(id));
216
+ }
217
+ },
218
+ /** Incremental UTF-8 decoder, including code points split across HTTP chunks. */
219
+ async *textStream(id, signal) {
220
+ const decoder = new TextDecoder();
221
+ for await (const bytes of network.bytes(id, signal)) {
222
+ const text = decoder.decode(bytes, { stream: true });
223
+ if (text)
224
+ yield text;
225
+ }
226
+ const tail = decoder.decode();
227
+ if (tail)
228
+ yield tail;
229
+ },
230
+ /** SSE framing only: no automatic reconnect/retry or Last-Event-ID requests. */
231
+ async *sse(id, signal) {
232
+ let pending = '', data = [], event = '', lastId = '', retry, size = 0, skipLf = false;
233
+ for await (const text of network.textStream(id, signal)) {
234
+ pending += skipLf && text.startsWith('\n') ? text.slice(1) : text;
235
+ skipLf = false;
236
+ for (;;) {
237
+ const at = pending.search(/[\r\n]/);
238
+ if (at < 0)
239
+ break;
240
+ skipLf = pending[at] === '\r' && at === pending.length - 1;
241
+ const line = pending.slice(0, at);
242
+ pending = pending.slice(at + (pending[at] === '\r' && pending[at + 1] === '\n' ? 2 : 1));
243
+ size += line.length;
244
+ if (size > 262144)
245
+ throw new Error('network_sse_event_too_large');
246
+ if (!line) {
247
+ if (data.length)
248
+ yield { data: data.join('\n'), event: event || 'message', id: lastId, ...(retry === undefined ? {} : { retry }) };
249
+ data = [];
250
+ event = '';
251
+ size = 0;
252
+ continue;
253
+ }
254
+ if (line.startsWith(':'))
255
+ continue;
256
+ const colon = line.indexOf(':');
257
+ const field = colon < 0 ? line : line.slice(0, colon);
258
+ let value = colon < 0 ? '' : line.slice(colon + 1);
259
+ if (value.startsWith(' '))
260
+ value = value.slice(1);
261
+ if (field === 'data')
262
+ data.push(value);
263
+ else if (field === 'event')
264
+ event = value;
265
+ else if (field === 'id' && !value.includes('\0'))
266
+ lastId = value;
267
+ else if (field === 'retry' && /^\d+$/.test(value) && Number.isSafeInteger(Number(value)))
268
+ retry = Number(value);
269
+ }
270
+ if (size + pending.length > 262144)
271
+ throw new Error('network_sse_event_too_large');
272
+ }
273
+ // SSE requires a blank line to dispatch; incomplete final events are discarded.
274
+ },
275
+ /** Explicitly dispose a stream that was never iterated (or has another owner). */
276
+ async dispose(id) {
277
+ const ignoreMissing = (error) => { if (!/^(operation_missing|network_job_missing|permission_denied)$/.test(error instanceof Error ? error.message : String(error)))
278
+ throw error; };
279
+ try {
280
+ await network.cancel(id);
281
+ }
282
+ catch (error) {
283
+ ignoreMissing(error);
284
+ }
285
+ try {
286
+ await network.release(id);
287
+ }
288
+ catch (error) {
289
+ ignoreMissing(error);
290
+ }
291
+ },
171
292
  async text(id) {
172
293
  const decoder = new TextDecoder();
173
294
  let offset = 0, text = '';
@@ -299,6 +420,20 @@ function mediaProvider(kind) {
299
420
  }
300
421
  export const camera = mediaProvider('camera'), microphone = mediaProvider('microphone'), systemAudio = mediaProvider('system_audio');
301
422
  export const accessibility = { status: () => nativeCall('accessibility.status'), request: () => nativeCall('accessibility.request'), windows: () => nativeCall('accessibility.windows'), focus: (id) => nativeCall('accessibility.focus', { id }), selectedText: (id) => nativeCall('accessibility.selected_text', { id }), insertText: (focus, text) => nativeCall('accessibility.insert_text', { id: focus, text }) };
423
+ /** Window management (macOS, permission `window_management`, OS Accessibility). All calls are
424
+ * operations: `operations.wait(await windowManagement.focused())`. `request` opens the system
425
+ * prompt and is window-only; the others work in the worker too. `focused` never returns a Medeo window:
426
+ * when Medeo is in front (a plugin's own page) it is the window of the app right behind it, and `null`
427
+ * when there is none or the frontmost app has no window; `set_frame` reports the frame the app
428
+ * actually accepted (apps may clamp sizes) and fails with `resource_missing` once the window is gone. */
429
+ export const windowManagement = {
430
+ status: () => nativeCall('window_management.status'),
431
+ request: () => nativeCall('window_management.request'),
432
+ focused: () => nativeCall('window_management.focused'),
433
+ setFrame: (id, frame) => nativeCall('window_management.set_frame', { id, x: frame.x, y: frame.y, width: frame.width, height: frame.height }),
434
+ screens: () => nativeCall('window_management.screens'),
435
+ pointer: () => nativeCall('window_management.pointer'),
436
+ };
302
437
  export const browserView = { open: (id, url, width = 1000, height = 720) => capabilities.invoke('browser.open', { id, url, width, height }), state: (id) => capabilities.invoke('browser.state', { id }), close: (id) => capabilities.invoke('browser.close', { id }) };
303
438
  export const printing = { print: (text) => capabilities.invoke('printing.print', { text }) };
304
439
  export const processes = { start: (executable, args = [], timeoutMs = 60000) => nativeCall('process.start', { executable, arguments: args, timeout_ms: timeoutMs }), state: (id) => nativeCall('process.state', { id }), write: (id, text) => nativeCall('process.write', { id, text }), stop: (id) => nativeCall('process.stop', { id }) };
@@ -0,0 +1,46 @@
1
+ /** React is only imported by the optional @medeo-plugin/sdk/ui/react entry. */
2
+ import { type ReactNode } from 'react';
3
+ import { type CardOptions, type PillOptions, type ToggleOptions, type SegmentedOptions, type ListOptions } from './ui.js';
4
+ export { uiStyles, installStyles } from './ui/styles.js';
5
+ export type { CardOptions, PillOptions, ToggleOptions, SegmentedOptions, ListOptions, Segment } from './ui.js';
6
+ /** Call once near your root. SSR-safe; style installation is idempotent. */
7
+ export declare function useUIStyles(): void;
8
+ export declare function Card({ title, description, testid, children }: CardOptions & {
9
+ children?: ReactNode;
10
+ }): import("react").DetailedReactHTMLElement<{
11
+ className: string;
12
+ 'data-testid': string;
13
+ 'aria-label': string;
14
+ }, HTMLElement>;
15
+ export declare function Pill({ label, tone, testid }: PillOptions): import("react").DetailedReactHTMLElement<{
16
+ className: string;
17
+ 'data-testid': string;
18
+ 'data-tone': "error" | "neutral" | "info";
19
+ }, HTMLElement>;
20
+ /** Controlled: update checked in onChange. Native input supplies name and Space handling. */
21
+ export declare function Toggle({ label, checked, disabled, onChange, testid }: ToggleOptions): import("react").DetailedReactHTMLElement<{
22
+ className: string;
23
+ 'data-testid': string;
24
+ }, HTMLElement>;
25
+ /** Controlled radio group with a single tab stop and arrow/Home/End navigation. */
26
+ export declare function Segmented<T extends string>({ label, value, options, disabled, onChange, testid }: SegmentedOptions<T>): import("react").DetailedReactHTMLElement<{
27
+ className: string;
28
+ role: "radiogroup";
29
+ 'aria-label': string;
30
+ 'data-testid': string;
31
+ }, HTMLElement>;
32
+ export declare function List({ label, testid, items }: ListOptions & {
33
+ items: readonly {
34
+ key: string;
35
+ content: ReactNode;
36
+ }[];
37
+ }): import("react").DetailedReactHTMLElement<{
38
+ className: string;
39
+ 'data-testid': string;
40
+ 'aria-label': string;
41
+ }, HTMLElement>;
42
+ export declare function EmptyState({ title, description, testid }: CardOptions): import("react").DetailedReactHTMLElement<{
43
+ className: string;
44
+ 'data-testid': string;
45
+ 'aria-label': string;
46
+ }, HTMLElement>;
package/dist/react.js ADDED
@@ -0,0 +1,44 @@
1
+ /** React is only imported by the optional @medeo-plugin/sdk/ui/react entry. */
2
+ import { createElement as h, useEffect } from 'react';
3
+ import { segmentTarget, segmentTabIndex } from './ui.js';
4
+ import { installStyles } from './ui/styles.js';
5
+ export { uiStyles, installStyles } from './ui/styles.js';
6
+ /** Call once near your root. SSR-safe; style installation is idempotent. */
7
+ export function useUIStyles() { useEffect(() => { installStyles(); }, []); }
8
+ export function Card({ title, description, testid, children }) {
9
+ return h('section', { className: 'medeo-ui medeo-card', 'data-testid': testid, 'aria-label': title }, h('h2', { 'data-testid': `${testid}.title` }, title), description !== undefined && h('p', { className: 'medeo-description' }, description), children);
10
+ }
11
+ export function Pill({ label, tone = 'info', testid }) {
12
+ return h('span', { className: 'medeo-ui medeo-pill', 'data-testid': testid, 'data-tone': tone }, label);
13
+ }
14
+ /** Controlled: update checked in onChange. Native input supplies name and Space handling. */
15
+ export function Toggle({ label, checked, disabled, onChange, testid }) {
16
+ return h('label', { className: 'medeo-ui medeo-toggle', 'data-testid': `${testid}.root` }, h('input', { type: 'checkbox', role: 'switch', checked, disabled, 'data-testid': testid, onChange: (event) => onChange(event.currentTarget.checked) }), h('span', null, label));
17
+ }
18
+ /** Controlled radio group with a single tab stop and arrow/Home/End navigation. */
19
+ export function Segmented({ label, value, options, disabled, onChange, testid }) {
20
+ return h('div', { className: 'medeo-ui medeo-segmented', role: 'radiogroup', 'aria-label': label, 'data-testid': testid }, options.map((option, index) => h('button', {
21
+ key: option.value, type: 'button', role: 'radio', 'aria-checked': option.value === value,
22
+ 'data-testid': `${testid}.${index}`, disabled: disabled || option.disabled,
23
+ tabIndex: disabled ? -1 : segmentTabIndex(options, value, index),
24
+ onClick: () => { if (!disabled && !option.disabled)
25
+ onChange(option.value); },
26
+ onKeyDown: (event) => {
27
+ if (disabled)
28
+ return;
29
+ const group = event.currentTarget.parentElement;
30
+ const target = segmentTarget(options, index, event.key, getComputedStyle(group).direction === 'rtl');
31
+ if (target === undefined)
32
+ return;
33
+ event.preventDefault();
34
+ group.children[target].focus();
35
+ onChange(options[target].value);
36
+ },
37
+ }, option.label)));
38
+ }
39
+ export function List({ label, testid, items }) {
40
+ return h('ul', { className: 'medeo-ui medeo-list', 'data-testid': testid, 'aria-label': label }, items.map(item => h('li', { key: item.key, 'data-testid': `${testid}.${item.key}` }, item.content)));
41
+ }
42
+ export function EmptyState({ title, description, testid }) {
43
+ return h('section', { className: 'medeo-ui medeo-empty', 'data-testid': testid, 'aria-label': title }, h('h2', null, title), description !== undefined && h('p', { className: 'medeo-description' }, description));
44
+ }
@@ -713,6 +713,48 @@ export declare const METHODS: {
713
713
  readonly confirm: false;
714
714
  readonly adapter: "host";
715
715
  };
716
+ readonly "window_management.status": {
717
+ readonly permission: "window_management";
718
+ readonly provider: "medeo.system-services";
719
+ readonly ui: false;
720
+ readonly confirm: false;
721
+ readonly adapter: "host";
722
+ };
723
+ readonly "window_management.request": {
724
+ readonly permission: "window_management";
725
+ readonly provider: "medeo.system-services";
726
+ readonly ui: true;
727
+ readonly confirm: false;
728
+ readonly adapter: "host";
729
+ };
730
+ readonly "window_management.focused": {
731
+ readonly permission: "window_management";
732
+ readonly provider: "medeo.system-services";
733
+ readonly ui: false;
734
+ readonly confirm: false;
735
+ readonly adapter: "host";
736
+ };
737
+ readonly "window_management.set_frame": {
738
+ readonly permission: "window_management";
739
+ readonly provider: "medeo.system-services";
740
+ readonly ui: false;
741
+ readonly confirm: false;
742
+ readonly adapter: "host";
743
+ };
744
+ readonly "window_management.screens": {
745
+ readonly permission: "window_management";
746
+ readonly provider: "medeo.system-services";
747
+ readonly ui: false;
748
+ readonly confirm: false;
749
+ readonly adapter: "host";
750
+ };
751
+ readonly "window_management.pointer": {
752
+ readonly permission: "window_management";
753
+ readonly provider: "medeo.system-services";
754
+ readonly ui: false;
755
+ readonly confirm: false;
756
+ readonly adapter: "host";
757
+ };
716
758
  readonly "input_method.status": {
717
759
  readonly permission: "input_method";
718
760
  readonly provider: "medeo.input-method";
package/dist/registry.js CHANGED
@@ -102,6 +102,12 @@ export const METHODS = {
102
102
  "accessibility.focus": { permission: "accessibility", provider: "medeo.system-services", ui: true, confirm: false, adapter: "host" },
103
103
  "accessibility.selected_text": { permission: "accessibility", provider: "medeo.system-services", ui: true, confirm: false, adapter: "host" },
104
104
  "accessibility.insert_text": { permission: "accessibility", provider: "medeo.system-services", ui: true, confirm: false, adapter: "host" },
105
+ "window_management.status": { permission: "window_management", provider: "medeo.system-services", ui: false, confirm: false, adapter: "host" },
106
+ "window_management.request": { permission: "window_management", provider: "medeo.system-services", ui: true, confirm: false, adapter: "host" },
107
+ "window_management.focused": { permission: "window_management", provider: "medeo.system-services", ui: false, confirm: false, adapter: "host" },
108
+ "window_management.set_frame": { permission: "window_management", provider: "medeo.system-services", ui: false, confirm: false, adapter: "host" },
109
+ "window_management.screens": { permission: "window_management", provider: "medeo.system-services", ui: false, confirm: false, adapter: "host" },
110
+ "window_management.pointer": { permission: "window_management", provider: "medeo.system-services", ui: false, confirm: false, adapter: "host" },
105
111
  "input_method.status": { permission: "input_method", provider: "medeo.input-method", ui: false, confirm: false, adapter: "host" },
106
112
  "input_method.configure": { permission: "input_method", provider: "medeo.input-method", ui: false, confirm: false, adapter: "host" },
107
113
  "input_method.enable": { permission: "input_method", provider: "medeo.input-method", ui: true, confirm: false, adapter: "host" },
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { type Disposable } from './index.ts';
2
+ /** A transport is injectable for offline tests; the default is this plugin's own channel. */
3
+ export interface RpcTransport {
4
+ post(message: unknown): Promise<void>;
5
+ onMessage(listener: (message: unknown) => void): () => void;
6
+ }
7
+ export interface RpcOptions {
8
+ transport?: RpcTransport;
9
+ timeoutMs?: number;
10
+ signal?: AbortSignal;
11
+ }
12
+ type Handler = (...args: any[]) => unknown;
13
+ export type WorkerApiClient<T> = {
14
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;
15
+ } & {
16
+ $dispose(): void;
17
+ };
18
+ /** Register in activate, then push the returned disposable into context.subscriptions.
19
+ * Requests only reach this plugin's worker; this is not a cross-plugin service API.
20
+ */
21
+ export declare function defineWorkerApi<T extends {
22
+ [K in keyof T]: Handler;
23
+ }>(handlers: T, options?: Pick<RpcOptions, 'transport'>): Disposable;
24
+ /** Create once per window, dispose on unmount. Timeout/disposal cancels waiting,
25
+ * not a worker side effect that already started. All arguments/results are JSON.
26
+ */
27
+ export declare function useWorkerApi<T>(options?: RpcOptions): WorkerApiClient<T>;
28
+ export {};