@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.
- package/dist/generated.d.ts +28 -1
- package/dist/index.d.ts +82 -4
- package/dist/index.js +137 -2
- package/dist/react.d.ts +46 -0
- package/dist/react.js +44 -0
- package/dist/registry.d.ts +42 -0
- package/dist/registry.js +6 -0
- package/dist/rpc.d.ts +28 -0
- package/dist/rpc.js +172 -0
- package/dist/testing.d.ts +28 -0
- package/dist/testing.js +161 -0
- package/dist/ui/styles.d.ts +4 -0
- package/dist/ui/styles.js +31 -0
- package/dist/ui.d.ts +50 -0
- package/dist/ui.js +115 -0
- package/package.json +29 -1
- package/src/generated.ts +28 -2
- package/src/index.ts +110 -7
- package/src/react.ts +50 -0
- package/src/registry.ts +6 -0
- package/src/rpc.ts +136 -0
- package/src/testing.ts +144 -0
- package/src/ui/styles.ts +31 -0
- package/src/ui.ts +102 -0
package/dist/generated.d.ts
CHANGED
|
@@ -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<
|
|
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 }) };
|
package/dist/react.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -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 {};
|