@odyn-app/api 0.0.0-stage → 0.1.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.
Files changed (37) hide show
  1. package/README.md +24 -2
  2. package/dist/index.d.ts +492 -0
  3. package/dist/index.js +487 -0
  4. package/dist/ui/button.d.ts +10 -0
  5. package/dist/ui/button.js +31 -0
  6. package/dist/ui/cn.d.ts +3 -0
  7. package/dist/ui/cn.js +6 -0
  8. package/dist/ui/index.d.ts +10 -0
  9. package/dist/ui/index.js +10 -0
  10. package/dist/ui/orb/orb-base.module.css +21 -0
  11. package/dist/ui/orb/orb-geometry-cache.d.ts +1 -0
  12. package/dist/ui/orb/orb-geometry-cache.js +11 -0
  13. package/dist/ui/orb/orb-helix-geometry.d.ts +7 -0
  14. package/dist/ui/orb/orb-helix-geometry.js +130 -0
  15. package/dist/ui/orb/orb-helix.module.css +155 -0
  16. package/dist/ui/orb/orb-lattice-geometry.d.ts +35 -0
  17. package/dist/ui/orb/orb-lattice-geometry.js +148 -0
  18. package/dist/ui/orb/orb-lattice.module.css +165 -0
  19. package/dist/ui/orb/orb-lens-primary.module.css +195 -0
  20. package/dist/ui/orb/orb-lens-secondary.module.css +143 -0
  21. package/dist/ui/orb/orb-lens.module.css +44 -0
  22. package/dist/ui/orb/orb-morph-geometry.d.ts +7 -0
  23. package/dist/ui/orb/orb-morph-geometry.js +88 -0
  24. package/dist/ui/orb/orb-morph.module.css +62 -0
  25. package/dist/ui/orb/orb-ring-geometry.d.ts +8 -0
  26. package/dist/ui/orb/orb-ring-geometry.js +42 -0
  27. package/dist/ui/orb/orb-ring.module.css +113 -0
  28. package/dist/ui/orb/orb-types.d.ts +23 -0
  29. package/dist/ui/orb/orb-types.js +48 -0
  30. package/dist/ui/orb/orb.d.ts +17 -0
  31. package/dist/ui/orb/orb.js +40 -0
  32. package/dist/ui/setting-row.d.ts +9 -0
  33. package/dist/ui/setting-row.js +9 -0
  34. package/dist/ui/skeleton.d.ts +3 -0
  35. package/dist/ui/skeleton.js +6 -0
  36. package/dist/ui/theme.css +232 -0
  37. package/package.json +56 -4
package/README.md CHANGED
@@ -1,3 +1,25 @@
1
- # Temporary Holding Version
1
+ # @odyn-app/api
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The building kit for [Odyn](https://github.com/New-Genre/odyn-extensions) extensions.
4
+
5
+ ```bash
6
+ bun add @odyn-app/api
7
+ ```
8
+
9
+ ```ts
10
+ import { defineExtension } from '@odyn-app/api'
11
+
12
+ defineExtension({
13
+ commands: {
14
+ 'say-hello': async () => ({ type: 'detail', markdown: 'Hello from my extension.' }),
15
+ },
16
+ })
17
+ ```
18
+
19
+ - `@odyn-app/api`: `defineExtension`, the `odyn` bridge, the manifest types and the manifest checker.
20
+ - `@odyn-app/api/ui`: Odyn's `Button`, `Skeleton`, `SettingRow`, `Orb` and `cn`, for extension pages.
21
+ - `@odyn-app/api/theme.css`: Odyn's colours, fonts and light/dark themes. Add `@import "@odyn-app/api/theme.css";` to a page's stylesheet.
22
+
23
+ Every extension runs sandboxed, and each call is checked against the permissions the user granted. To make one and publish it to Odyn's Marketplace, see the [contribution guide](https://github.com/New-Genre/odyn-extensions/blob/main/CONTRIBUTING.md).
24
+
25
+ MIT licence.
@@ -0,0 +1,492 @@
1
+ /** The newest building-kit version. Bumped when something new is added. */
2
+ export declare const EXTENSION_API_VERSION = 4;
3
+ /** The oldest building-kit version Odyn still runs. */
4
+ export declare const EXTENSION_API_MIN_VERSION = 1;
5
+ /** Everything an extension can ask the user to allow. */
6
+ export declare const EXTENSION_PERMISSIONS: readonly ["chats:read", "projects:read", "agents:start", "network", "composer:attach", "screenshots:read", "programs:run", "agent:tools"];
7
+ export type ExtensionPermission = (typeof EXTENSION_PERMISSIONS)[number];
8
+ /** Permissions only extensions made by Odyn may ask for. */
9
+ export declare const OFFICIAL_ONLY_PERMISSIONS: readonly ExtensionPermission[];
10
+ /** Systems an extension can say it runs on. Leave out to run everywhere. */
11
+ export declare const EXTENSION_PLATFORMS: readonly ["darwin", "linux", "win32"];
12
+ export type ExtensionPlatform = (typeof EXTENSION_PLATFORMS)[number];
13
+ /** Where an extension can show up inside Odyn. */
14
+ export declare const EXTENSION_PLACEMENTS: readonly ["sidebarPanel", "chatCard", "agentTool", "settings"];
15
+ export type ExtensionPlacement = (typeof EXTENSION_PLACEMENTS)[number];
16
+ export interface ExtensionCommand {
17
+ /** Unique within the extension, e.g. "show-standup". */
18
+ readonly name: string;
19
+ readonly title: string;
20
+ readonly placement: ExtensionPlacement;
21
+ /** For `agentTool`: what the agent is told the tool does. */
22
+ readonly description?: string;
23
+ /** For `agentTool`: what the agent fills in when it calls the tool. Needs `apiVersion` 4. */
24
+ readonly inputs?: readonly ExtensionCommandInput[];
25
+ }
26
+ /** The kinds of value an agent tool's input can take. */
27
+ export declare const EXTENSION_INPUT_TYPES: readonly ["string", "number", "boolean"];
28
+ export type ExtensionInputType = (typeof EXTENSION_INPUT_TYPES)[number];
29
+ /** One thing the agent fills in when it calls an agent tool. */
30
+ export interface ExtensionCommandInput {
31
+ /** Letters, digits and underscores, starting with a letter, e.g. "city". */
32
+ readonly name: string;
33
+ /** What the agent is told to put here. */
34
+ readonly description: string;
35
+ readonly type: ExtensionInputType;
36
+ readonly required?: boolean;
37
+ }
38
+ /** A value the agent or a form handed to a command. */
39
+ export type ExtensionInputValue = string | number | boolean;
40
+ /** Where an extension's own page can be shown. */
41
+ export declare const EXTENSION_SURFACE_PLACEMENTS: readonly ["composer", "settings"];
42
+ export type ExtensionSurfacePlacement = (typeof EXTENSION_SURFACE_PLACEMENTS)[number];
43
+ /** The icons Odyn can draw on the button that opens a page. */
44
+ export declare const EXTENSION_SURFACE_ICONS: readonly ["puzzle", "pencil", "image", "note"];
45
+ export type ExtensionSurfaceIcon = (typeof EXTENSION_SURFACE_ICONS)[number];
46
+ export declare const SURFACE_MIN_HEIGHT = 120;
47
+ export declare const SURFACE_MAX_HEIGHT = 600;
48
+ /**
49
+ * The extension's own page. Odyn shows it in a sealed box: no internet, no
50
+ * access to Odyn beyond `connectSurface()`, and no way out of its box.
51
+ */
52
+ export interface ExtensionSurface {
53
+ /** Unique within the extension, e.g. "canvas". */
54
+ readonly id: string;
55
+ readonly title: string;
56
+ /**
57
+ * `composer`: slides open above the message box, with a toolbar button.
58
+ * `settings`: shown inside the extension's settings window.
59
+ */
60
+ readonly placement: ExtensionSurfacePlacement;
61
+ /** The page inside the extension's folder, e.g. "dist/panel/index.html". */
62
+ readonly entry: string;
63
+ readonly icon?: ExtensionSurfaceIcon;
64
+ /** Height in pixels. Defaults to 260. */
65
+ readonly height?: number;
66
+ }
67
+ export interface ExtensionPreference {
68
+ readonly name: string;
69
+ readonly title: string;
70
+ readonly type: 'text' | 'password' | 'checkbox';
71
+ readonly required?: boolean;
72
+ }
73
+ /**
74
+ * A Mac program a helper may run. Odyn looks in `locations` in order and uses
75
+ * the first that exists. A location may start with `~`, `$DATA` (the
76
+ * extension's own data folder), `$USER_DATA` (Odyn's), or an environment
77
+ * variable such as `$ANDROID_HOME`; a bare name is looked up on PATH.
78
+ */
79
+ export interface ExtensionProgram {
80
+ /** Unique within the extension, e.g. "adb". */
81
+ readonly name: string;
82
+ readonly locations: readonly string[];
83
+ /** Put this program's folder on the agent's PATH while the extension is on. */
84
+ readonly exposeToAgent?: boolean;
85
+ }
86
+ /** A folder a helper may look up (never read or write), found like a program. */
87
+ export interface ExtensionFolder {
88
+ readonly name: string;
89
+ readonly locations: readonly string[];
90
+ }
91
+ /** The official-only background helper. Needs `programs:run` and `apiVersion` 3. */
92
+ export interface ExtensionHelper {
93
+ /** The built helper script, e.g. "dist/helper.js". */
94
+ readonly entry: string;
95
+ readonly programs: readonly ExtensionProgram[];
96
+ readonly folders?: readonly ExtensionFolder[];
97
+ /** Exact links the helper may open, e.g. an App Store page. */
98
+ readonly links?: readonly string[];
99
+ /** Hosts `download` may fetch from (redirects must stay inside the list). */
100
+ readonly downloadHosts?: readonly string[];
101
+ }
102
+ /** A command the agent must ask before using, e.g. `{ program: 'xcrun', subcommand: 'simctl' }`. */
103
+ export interface ExtensionGatedCommand {
104
+ readonly program: string;
105
+ readonly subcommand?: string;
106
+ }
107
+ /**
108
+ * What the extension gives the agent. Needs `agent:tools` and `apiVersion` 3.
109
+ * From version 4 the guide is optional: an extension may give only tools.
110
+ */
111
+ export interface ExtensionAgent {
112
+ /** The guide's name, which is also what the user types, e.g. "odyn-phone" → `/odyn-phone`. */
113
+ readonly skill?: string;
114
+ /** The folder holding the guide's SKILL.md, e.g. "skill". */
115
+ readonly skillFolder?: string;
116
+ /** One sentence telling the agent what this gives it. */
117
+ readonly summary: string;
118
+ /** Commands that need the user's OK in a chat first. */
119
+ readonly gatedCommands?: readonly ExtensionGatedCommand[];
120
+ /** What the agent wants to do, for the one-click card, e.g. "use a phone". */
121
+ readonly accessLabel?: string;
122
+ }
123
+ export interface ExtensionManifest {
124
+ /** Unique store id, lowercase with dashes, e.g. "standup-notes". */
125
+ readonly id: string;
126
+ readonly title: string;
127
+ readonly description: string;
128
+ readonly author: string;
129
+ readonly version: string;
130
+ /** The building-kit version this was built against. */
131
+ readonly apiVersion: number;
132
+ readonly permissions: readonly ExtensionPermission[];
133
+ /** Hosts `fetch` may reach. Required when `network` is asked for. */
134
+ readonly allowedHosts?: readonly string[];
135
+ readonly commands: readonly ExtensionCommand[];
136
+ /** Its own pages. Needs `apiVersion` 2. */
137
+ readonly surfaces?: readonly ExtensionSurface[];
138
+ readonly preferences?: readonly ExtensionPreference[];
139
+ /** Systems it runs on; hidden elsewhere. Needs `apiVersion` 3. */
140
+ readonly platforms?: readonly ExtensionPlatform[];
141
+ /** Background helper. Official extensions only. */
142
+ readonly helper?: ExtensionHelper;
143
+ /** A guide and permission-checked commands for the agent. */
144
+ readonly agent?: ExtensionAgent;
145
+ }
146
+ export interface ExtensionAction {
147
+ readonly id: string;
148
+ readonly title: string;
149
+ readonly style?: 'default' | 'destructive';
150
+ }
151
+ export interface ListItem {
152
+ readonly id: string;
153
+ readonly title: string;
154
+ readonly subtitle?: string;
155
+ readonly accessory?: string;
156
+ readonly actions?: readonly ExtensionAction[];
157
+ }
158
+ export type ExtensionView = {
159
+ readonly type: 'list';
160
+ readonly items: readonly ListItem[];
161
+ readonly emptyText?: string;
162
+ } | {
163
+ readonly type: 'detail';
164
+ readonly markdown: string;
165
+ readonly actions?: readonly ExtensionAction[];
166
+ } | {
167
+ readonly type: 'form';
168
+ readonly fields: readonly {
169
+ readonly name: string;
170
+ readonly title: string;
171
+ readonly type: 'text' | 'textarea' | 'checkbox';
172
+ }[];
173
+ readonly submitTitle: string;
174
+ };
175
+ export interface ChatSummary {
176
+ readonly id: string;
177
+ readonly projectId: string;
178
+ readonly title: string;
179
+ readonly updatedAt: string;
180
+ }
181
+ export interface ProjectSummary {
182
+ readonly id: string;
183
+ readonly name: string;
184
+ }
185
+ export interface ChatMessageSummary {
186
+ readonly role: 'user' | 'assistant' | 'system';
187
+ readonly content: string;
188
+ readonly createdAt: string;
189
+ }
190
+ /** Odyn's side of the conversation. Every call is permission-checked by Odyn. */
191
+ export interface OdynApi {
192
+ readonly chats: {
193
+ list(projectId?: string): Promise<readonly ChatSummary[]>;
194
+ messages(chatId: string, limit?: number): Promise<readonly ChatMessageSummary[]>;
195
+ /** The chat the user is looking at, if any. */
196
+ current(): Promise<ChatSummary | null>;
197
+ };
198
+ readonly projects: {
199
+ list(): Promise<readonly ProjectSummary[]>;
200
+ };
201
+ readonly agents: {
202
+ /** Sends a prompt to a chat, starting the agent there. */
203
+ start(chatId: string, prompt: string): Promise<void>;
204
+ };
205
+ /** `fetch`, limited to the manifest's `allowedHosts`. */
206
+ fetch(url: string, init?: {
207
+ readonly method?: string;
208
+ readonly headers?: Record<string, string>;
209
+ readonly body?: string;
210
+ }): Promise<{
211
+ readonly status: number;
212
+ readonly text: string;
213
+ }>;
214
+ showToast(message: string): Promise<void>;
215
+ /** The values the user saved in the extension's settings. */
216
+ preferences(): Promise<Readonly<Record<string, string | boolean>>>;
217
+ }
218
+ /** What a command receives when it runs. */
219
+ export interface CommandContext {
220
+ readonly odyn: OdynApi;
221
+ /** Set when the user picked an action on something the command drew. */
222
+ readonly actionId?: string;
223
+ readonly itemId?: string;
224
+ /** Set when a form was submitted, or the agent called an agent tool. */
225
+ readonly input?: Readonly<Record<string, ExtensionInputValue>>;
226
+ }
227
+ /** Draws a view, or (for an agent tool) returns text for the agent. */
228
+ export type CommandHandler = (context: CommandContext) => Promise<ExtensionView | string | void>;
229
+ export interface ExtensionDefinition {
230
+ readonly commands: Readonly<Record<string, CommandHandler>>;
231
+ }
232
+ /** A command run Odyn asks the extension to do. */
233
+ export interface ExtensionRunRequest {
234
+ readonly callId: string;
235
+ readonly command: string;
236
+ readonly actionId?: string;
237
+ readonly itemId?: string;
238
+ readonly input?: Readonly<Record<string, ExtensionInputValue>>;
239
+ }
240
+ export type ExtensionRunResult = {
241
+ readonly ok: true;
242
+ readonly value: ExtensionView | string | null;
243
+ } | {
244
+ readonly ok: false;
245
+ readonly error: string;
246
+ };
247
+ /** The bridge Odyn's loader places on the page before an extension runs. */
248
+ export interface ExtensionBridge {
249
+ call(method: string, args: readonly unknown[]): Promise<unknown>;
250
+ onRun(listener: (request: ExtensionRunRequest) => void): void;
251
+ finish(callId: string, result: ExtensionRunResult): void;
252
+ }
253
+ /** Every method name the bridge accepts, so Odyn can refuse anything else. */
254
+ export declare const EXTENSION_API_METHODS: readonly ["chats.list", "chats.messages", "chats.current", "projects.list", "agents.start", "fetch", "showToast", "preferences"];
255
+ export type ExtensionApiMethod = (typeof EXTENSION_API_METHODS)[number];
256
+ /**
257
+ * The one call an extension's entry file makes. Inside Odyn it hooks the
258
+ * commands up to Odyn; anywhere else (tests, the store checker) it just
259
+ * hands the definition back.
260
+ */
261
+ export declare function defineExtension(definition: ExtensionDefinition): ExtensionDefinition;
262
+ /** Returns every problem with a manifest; an empty list means it is fine. */
263
+ export interface ManifestCheckOptions {
264
+ /**
265
+ * Made by Odyn and shipped inside the app. An official extension may be
266
+ * nothing but an on/off switch for a feature built into Odyn, so it doesn't
267
+ * need a command, page, helper or agent section.
268
+ */
269
+ readonly official?: boolean;
270
+ }
271
+ export declare function validateManifest(value: unknown, options?: ManifestCheckOptions): readonly string[];
272
+ /** Every message between a page and Odyn carries this tag. */
273
+ export declare const SURFACE_MESSAGE_TAG = "odyn-surface";
274
+ /** Every call a page can make, so Odyn can refuse anything else. */
275
+ export declare const EXTENSION_SURFACE_METHODS: readonly ["chatStorage.get", "chatStorage.set", "composer.attachImage", "helper.call"];
276
+ export type ExtensionSurfaceMethod = (typeof EXTENSION_SURFACE_METHODS)[number];
277
+ /** The most a page may keep for one chat, and the largest picture it may attach. */
278
+ export declare const SURFACE_STORAGE_MAX_BYTES = 5000000;
279
+ export declare const SURFACE_IMAGE_MAX_BYTES = 10000000;
280
+ /** What the page is showing for. Sent on open and whenever it changes. */
281
+ export interface SurfaceContext {
282
+ readonly chatId: string | null;
283
+ readonly theme: 'light' | 'dark';
284
+ }
285
+ /** A picture handed over by Odyn (a screenshot) or attached by the page. */
286
+ export interface SurfaceImage {
287
+ readonly name: string;
288
+ readonly mimeType: string;
289
+ readonly bytes: Uint8Array;
290
+ }
291
+ /** Messages a page sends to Odyn. */
292
+ export type SurfaceOutgoingMessage = {
293
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
294
+ readonly kind: 'ready';
295
+ } | {
296
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
297
+ readonly kind: 'close';
298
+ } | {
299
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
300
+ readonly kind: 'call';
301
+ readonly id: string;
302
+ readonly method: ExtensionSurfaceMethod;
303
+ readonly args: readonly unknown[];
304
+ };
305
+ /** Messages Odyn sends to a page. */
306
+ export type SurfaceIncomingMessage = {
307
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
308
+ readonly kind: 'context';
309
+ readonly context: SurfaceContext;
310
+ } | {
311
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
312
+ readonly kind: 'image';
313
+ readonly image: SurfaceImage;
314
+ } | {
315
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
316
+ readonly kind: 'helperMessage';
317
+ readonly message: unknown;
318
+ } | {
319
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
320
+ readonly kind: 'result';
321
+ readonly id: string;
322
+ readonly ok: boolean;
323
+ readonly value?: unknown;
324
+ readonly error?: string;
325
+ };
326
+ /** What a page can do. Get it with `connectSurface()`. */
327
+ export interface OdynSurfaceApi {
328
+ /** Called straight away with the current chat and theme, then on every change. */
329
+ onContext(listener: (context: SurfaceContext) => void): () => void;
330
+ /** Called when the user hands the page a screenshot. Needs `screenshots:read`. */
331
+ onImage(listener: (image: SurfaceImage) => void): () => void;
332
+ /** A small text store per chat, deleted with the chat or when the extension is switched off. */
333
+ readonly chatStorage: {
334
+ get(chatId: string, key: string): Promise<string | null>;
335
+ /** `null` removes the value. */
336
+ set(chatId: string, key: string, value: string | null): Promise<void>;
337
+ };
338
+ readonly composer: {
339
+ /** Puts a picture onto the user's message. Needs `composer:attach`. */
340
+ attachImage(image: SurfaceImage): Promise<void>;
341
+ /** Closes the page. */
342
+ close(): void;
343
+ };
344
+ /** The extension's own helper, when it has one. */
345
+ readonly helper: {
346
+ /** Asks the helper to do something it registered with `odyn.pages.handle`. */
347
+ call<T = unknown>(name: string, ...args: readonly unknown[]): Promise<T>;
348
+ /** Called when the helper posts to the extension's open pages. */
349
+ onMessage(listener: (message: unknown) => void): () => void;
350
+ };
351
+ }
352
+ export declare function isSurfaceMessage(value: unknown): value is {
353
+ readonly tag: typeof SURFACE_MESSAGE_TAG;
354
+ readonly kind: string;
355
+ };
356
+ /** The one call a page makes to start talking to Odyn. */
357
+ export declare function connectSurface(): OdynSurfaceApi;
358
+ /** Devices Odyn knows how to draw a live screen for. */
359
+ export declare const SCREEN_SESSION_DEVICES: readonly ["iphone", "android"];
360
+ export type ScreenSessionDevice = (typeof SCREEN_SESSION_DEVICES)[number];
361
+ /** A live screen the helper shows in the sidebar, tied to the chat it belongs to. */
362
+ export interface ScreenSession {
363
+ /** Unique within the extension. */
364
+ readonly id: string;
365
+ readonly chatId: string;
366
+ readonly title: string;
367
+ readonly device: ScreenSessionDevice;
368
+ }
369
+ /** Taps and swipes arrive as fractions of the screen (0 to 1). */
370
+ export type ScreenInput = {
371
+ readonly kind: 'tap';
372
+ readonly x: number;
373
+ readonly y: number;
374
+ } | {
375
+ readonly kind: 'swipe';
376
+ readonly fromX: number;
377
+ readonly fromY: number;
378
+ readonly toX: number;
379
+ readonly toY: number;
380
+ } | {
381
+ readonly kind: 'type';
382
+ readonly text: string;
383
+ } | {
384
+ readonly kind: 'home';
385
+ } | {
386
+ readonly kind: 'button';
387
+ readonly button: 'power' | 'volume-up' | 'volume-down';
388
+ };
389
+ export interface ProgramRunOptions {
390
+ /** Extra environment variables. */
391
+ readonly env?: Readonly<Record<string, string>>;
392
+ /** Milliseconds. Defaults to 15 seconds; at most an hour. */
393
+ readonly timeoutMs?: number;
394
+ /** Written to the program's input once, repeated `stdinRepeat` times (for licence prompts). */
395
+ readonly stdin?: string;
396
+ readonly stdinRepeat?: number;
397
+ /** Return what the program prints as bytes instead of text. */
398
+ readonly binary?: boolean;
399
+ /**
400
+ * Odyn makes a scratch file, puts its path wherever an argument is
401
+ * `$OUTPUT_FILE`, and hands the file's bytes back as `file`.
402
+ */
403
+ readonly outputFile?: boolean;
404
+ }
405
+ export interface ProgramResult {
406
+ readonly exitCode: number;
407
+ readonly stdout: string;
408
+ /** The last part of what the program printed as errors. */
409
+ readonly stderr: string;
410
+ /** Set with `binary` (what it printed) or `outputFile` (the file's bytes). */
411
+ readonly bytes?: Uint8Array;
412
+ }
413
+ /** What a helper can ask Odyn to do. */
414
+ export interface OdynHelperApi {
415
+ readonly programs: {
416
+ /** Where a listed program is, or null when none of its locations exist. */
417
+ find(name: string): Promise<string | null>;
418
+ /** Runs a listed program and waits for it. Arguments may use `$DATA` for the data folder. */
419
+ run(name: string, args: readonly string[], options?: ProgramRunOptions): Promise<ProgramResult>;
420
+ /** Starts a listed program in the background and does not wait. */
421
+ launch(name: string, args: readonly string[], options?: {
422
+ readonly env?: Readonly<Record<string, string>>;
423
+ }): Promise<void>;
424
+ };
425
+ readonly folders: {
426
+ /** Where a listed folder is, or null. */
427
+ find(name: string): Promise<string | null>;
428
+ };
429
+ /** The extension's own data folder: kept across updates, deleted on uninstall. */
430
+ readonly data: {
431
+ path(): Promise<string>;
432
+ /** Downloads into the data folder, checking an `sha512-…` integrity value when given. */
433
+ download(url: string, relativePath: string, integrity?: string): Promise<void>;
434
+ remove(relativePath: string): Promise<void>;
435
+ makeFolder(relativePath: string): Promise<void>;
436
+ };
437
+ /** Opens one of the manifest's `helper.links`. */
438
+ openLink(url: string): Promise<void>;
439
+ showToast(message: string): Promise<void>;
440
+ readonly sessions: {
441
+ /** Replaces the whole list of live screens. */
442
+ set(sessions: readonly ScreenSession[]): Promise<void>;
443
+ /** Sends a new picture of a screen that someone is watching. */
444
+ frame(sessionId: string, bytes: Uint8Array, mimeType: 'image/png' | 'image/jpeg'): Promise<void>;
445
+ };
446
+ readonly pages: {
447
+ /** Answers `helper.call(name, …)` from the extension's pages. */
448
+ handle(name: string, handler: (...args: readonly unknown[]) => Promise<unknown> | unknown): void;
449
+ /** Sends a message to every open page of this extension. */
450
+ post(message: unknown): Promise<void>;
451
+ };
452
+ }
453
+ /** Things that happen, delivered to the helper. */
454
+ export interface HelperEvents {
455
+ /** Someone opened a live screen; send frames until `unwatch`. */
456
+ watch?(sessionId: string): void;
457
+ unwatch?(sessionId: string): void;
458
+ input?(sessionId: string, input: ScreenInput): Promise<void> | void;
459
+ /** The user pressed Close on a live screen. */
460
+ close?(sessionId: string): Promise<void> | void;
461
+ /** The agent ran a shell command using one of the extension's gated commands. */
462
+ agentCommand?(chatId: string, command: string): void;
463
+ /** A chat was archived or deleted. */
464
+ chatClosed?(chatId: string): Promise<void> | void;
465
+ /** The extension is being switched off or Odyn is quitting. Clean up quickly. */
466
+ shutdown?(): Promise<void> | void;
467
+ }
468
+ export interface HelperDefinition extends HelperEvents {
469
+ start(odyn: OdynHelperApi): Promise<void> | void;
470
+ }
471
+ /** Every call a helper can make, so Odyn can refuse anything else. */
472
+ export declare const EXTENSION_HELPER_METHODS: readonly ["programs.find", "programs.run", "programs.launch", "folders.find", "data.path", "data.download", "data.remove", "data.makeFolder", "openLink", "showToast", "sessions.set", "sessions.frame", "pages.post"];
473
+ export type ExtensionHelperMethod = (typeof EXTENSION_HELPER_METHODS)[number];
474
+ /** The events Odyn sends a helper. */
475
+ export declare const EXTENSION_HELPER_EVENTS: readonly ["watch", "unwatch", "input", "close", "agentCommand", "chatClosed", "shutdown", "pageCall"];
476
+ export type ExtensionHelperEvent = (typeof EXTENSION_HELPER_EVENTS)[number];
477
+ /** A helper's answer to an event that waits for one (input, close, page calls). */
478
+ export type HelperReply = {
479
+ readonly ok: true;
480
+ readonly value: unknown;
481
+ } | {
482
+ readonly ok: false;
483
+ readonly error: string;
484
+ };
485
+ /** The bridge Odyn's loader places on a helper's page. */
486
+ export interface HelperBridge {
487
+ call(method: ExtensionHelperMethod, args: readonly unknown[]): Promise<unknown>;
488
+ onEvent(listener: (event: ExtensionHelperEvent, args: readonly unknown[], replyId: string | null) => void): void;
489
+ reply(replyId: string, result: HelperReply): void;
490
+ }
491
+ /** The one call a helper's script makes. Anywhere but Odyn it just hands the definition back. */
492
+ export declare function defineHelper(definition: HelperDefinition): HelperDefinition;