@open-pets/plugin-sdk 2.1.1 → 3.3.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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Type definitions for OpenPets plugins.
2
+ * Type definitions for OpenPets plugins — SDK v3 ("SuperPlugins").
3
3
  *
4
4
  * This package ships **types only**. There is no runtime to import — the
5
5
  * OpenPets desktop app injects the `OpenPetsPlugin` global into the plugin
@@ -12,34 +12,484 @@
12
12
  *
13
13
  * OpenPetsPlugin.register({
14
14
  * async start(ctx) {
15
- * await ctx.pet.speak("Hello!")
15
+ * const bubble = await ctx.ui.bubble({ text: "Hello!", sticky: true })
16
+ * bubble.onAction(() => bubble.dismiss())
16
17
  * },
17
18
  * })
18
19
  * ```
19
20
  *
21
+ * SDK v3 reframes a pet as a programmable surface: the API is organized into
22
+ * subsystems (`ui`, `pets`, `events`, `audio`, `ai`, …) instead of one flat
23
+ * `ctx.pet` namespace. The v2 surface (`ctx.pet.speak`, `ctx.http.fetch`, …)
24
+ * keeps working unchanged.
25
+ *
20
26
  * @packageDocumentation
21
27
  */
22
28
  /** Capabilities a plugin can request in its manifest's `permissions` array. */
23
- export type OpenPetsPermission = "pet:speak" | "pet:reaction" | "pet:move" | "schedule" | "storage" | "status" | "commands" | "network";
29
+ export type OpenPetsPermission = "pet:speak" | "pet:reaction" | "pet:move" | "schedule" | "storage" | "status" | "commands" | "network" | "pet:interact" | "pet:pin" | "pet:animate" | "pet:speak:dynamic" | "pet:drop" | "pets:read" | "pets:manage" | "audio" | "events" | "ui:toast" | "ui:panel" | "ui:delivery" | "notify" | "bus" | "ai" | "secrets" | "voice:speak" | "voice:listen" | "auth" | "files" | "system:openExternal" | "system:metrics" | "clipboard" | "network:write";
24
30
  /**
25
- * Reaction name passed to {@link OpenPetsPetApi.react}. Common values are
31
+ * Reaction name passed to {@link OpenPetsPetHandle.react}. Common values are
26
32
  * listed for autocomplete; any string the host accepts is allowed.
27
33
  */
28
34
  export type OpenPetsReaction = "idle" | "thinking" | "working" | "editing" | "running" | "testing" | "waiting" | "waving" | "success" | "error" | "celebrating" | (string & {});
29
- /** Tone shown alongside a plugin's status line in the Plugins window. */
35
+ /** Tone shown alongside a plugin's status line or bubble. */
30
36
  export type OpenPetsStatusTone = "info" | "success" | "warning" | "error";
31
37
  /** A status line, either a bare string or text plus a {@link OpenPetsStatusTone}. */
32
38
  export type OpenPetsStatus = string | {
33
39
  text: string;
34
40
  tone?: OpenPetsStatusTone;
35
41
  };
36
- /** A single field in a command's input form. */
42
+ /** A point in screen coordinates. */
43
+ export interface OpenPetsPoint {
44
+ x: number;
45
+ y: number;
46
+ }
47
+ /** A rectangle in screen coordinates. */
48
+ export interface OpenPetsRect {
49
+ x: number;
50
+ y: number;
51
+ width: number;
52
+ height: number;
53
+ }
54
+ /**
55
+ * An opaque reference to an asset bundled with the plugin and declared in the
56
+ * manifest's `assets` block. Obtained from {@link OpenPetsAssetsApi}; never
57
+ * constructed by hand. Plugins pass references, never raw bytes or markup.
58
+ */
59
+ export interface OpenPetsAssetRef {
60
+ readonly kind: "icon" | "image" | "svg" | "sprite" | "sound";
61
+ readonly name: string;
62
+ }
63
+ /** A named host icon (curated set) or a bundled icon asset reference. */
64
+ export type OpenPetsIconRef = string | OpenPetsAssetRef;
65
+ /** A JSON-safe reference to a user-imported sound. */
66
+ export interface OpenPetsUserSoundRef {
67
+ readonly kind: "user-sound";
68
+ readonly id: string;
69
+ readonly name?: string;
70
+ }
71
+ /** A named host sound, bundled sound asset, or user-imported sound reference. */
72
+ export type OpenPetsSoundRef = string | OpenPetsAssetRef | OpenPetsUserSoundRef;
73
+ /** Resolve manifest-declared assets to opaque references. */
74
+ export interface OpenPetsAssetsApi {
75
+ /** Resolve a manifest-declared icon. */
76
+ icon(name: string): OpenPetsAssetRef;
77
+ /** Resolve a manifest-declared raster image. */
78
+ image(name: string): OpenPetsAssetRef;
79
+ /** Resolve a manifest-declared, install-time-sanitized SVG. */
80
+ svg(name: string): OpenPetsAssetRef;
81
+ /** Resolve a manifest-declared spritesheet. */
82
+ sprite(name: string): OpenPetsAssetRef;
83
+ /** Resolve a manifest-declared sound. */
84
+ sound(name: string): OpenPetsAssetRef;
85
+ }
86
+ /** A button rendered inside a bubble. Requires `pet:interact`. */
87
+ export interface OpenPetsBubbleAction {
88
+ /** Short, stable id (`[A-Za-z0-9._:-]`, 1–64 chars). */
89
+ id: string;
90
+ label: string;
91
+ style?: "default" | "primary" | "danger";
92
+ icon?: OpenPetsIconRef;
93
+ /** Dismiss the bubble when this action fires. Default true. */
94
+ dismissesBubble?: boolean;
95
+ }
96
+ /** An inline input rendered inside a bubble. Requires `pet:interact`. */
97
+ export interface OpenPetsBubbleInput {
98
+ id: string;
99
+ type: "text" | "number" | "select";
100
+ placeholder?: string;
101
+ default?: string | number;
102
+ /** Options for `select` inputs. */
103
+ options?: Array<{
104
+ value: string;
105
+ label: string;
106
+ }>;
107
+ submitLabel?: string;
108
+ }
109
+ /** A HUD item shown in the host-rendered pinned mini-HUD bubble. */
110
+ export interface OpenPetsBubbleHudItem {
111
+ /** Named host icon or bundled icon asset reference. */
112
+ icon: OpenPetsIconRef;
113
+ /** Numeric value between 0 and 100 inclusive. */
114
+ value: number;
115
+ /** Optional short display label. */
116
+ label?: string;
117
+ /** Optional theme color tone for the bar/indicator. */
118
+ tone?: "amber" | "blue" | "green" | "pink" | "slate" | "red";
119
+ }
120
+ /** Descriptor for a host-rendered mini HUD layout, used in pinned bubbles. */
121
+ export interface OpenPetsBubbleHud {
122
+ /** List of HUD items (usually up to 4 items). */
123
+ items: OpenPetsBubbleHudItem[];
124
+ }
125
+ /**
126
+ * A structured, host-rendered bubble descriptor. The plugin describes; the
127
+ * host renders — no raw HTML or live DOM ever crosses the SDK boundary.
128
+ */
129
+ export interface OpenPetsBubble {
130
+ /** Host-rendered mini HUD layout for pinned bubbles. */
131
+ hud?: OpenPetsBubbleHud;
132
+ /** Plain text, length-capped, content-filtered. */
133
+ text?: string;
134
+ /** Limited markdown (bold/italic/code/line breaks), host-sanitized. */
135
+ markdown?: string;
136
+ /** Named host icon or a bundled icon asset. Body media is icon-only; use `indicator` for icon + message. */
137
+ icon?: OpenPetsIconRef;
138
+ /** Bundled, install-time-sanitized SVG. Body media is icon-only; use `indicator` for icon + message. */
139
+ svg?: OpenPetsAssetRef;
140
+ /** Bundled raster image. Body media is icon-only; use `indicator` for icon + message. */
141
+ image?: OpenPetsAssetRef;
142
+ tone?: OpenPetsStatusTone;
143
+ /** Optional top-row indicator/header. Intended primarily for alerts. */
144
+ indicator?: OpenPetsAlertIndicator | false;
145
+ /** Theme-token name, not a raw color string. */
146
+ accent?: string;
147
+ /**
148
+ * Marks content as model-generated. Requires `pet:speak:dynamic` and the
149
+ * global "allow AI speech" setting; uses the relaxed dynamic screen instead
150
+ * of the static ambient filter (§13.1).
151
+ */
152
+ dynamic?: boolean;
153
+ /** Auto-dismiss after N ms; omit for the host default. */
154
+ durationMs?: number;
155
+ /** Ignore duration; stay until dismissed or replaced. */
156
+ sticky?: boolean;
157
+ /** Occupy the persistent pinned slot (max 1 per pet). Requires `pet:pin`. */
158
+ pin?: boolean;
159
+ dismissOn?: Array<"timeout" | "click" | "petClick" | "action" | "outsideClick">;
160
+ /** Feeds the bubble arbiter. Default "normal". */
161
+ priority?: "low" | "normal" | "high" | "urgent";
162
+ actions?: OpenPetsBubbleAction[];
163
+ input?: OpenPetsBubbleInput;
164
+ }
165
+ /** Why a bubble went away. */
166
+ export type OpenPetsBubbleDismissReason = "timeout" | "click" | "replaced" | "manual" | "unpinned";
167
+ /**
168
+ * Live handle to a shown bubble. Lets the plugin update it in place
169
+ * (progress, countdowns, streamed tokens) and receive interaction callbacks.
170
+ */
171
+ export interface OpenPetsBubbleHandle {
172
+ readonly id: string;
173
+ update(patch: Partial<OpenPetsBubble>): Promise<void>;
174
+ dismiss(): Promise<void>;
175
+ /** Promote this bubble to the pinned slot (§1.5). Requires `pet:pin`. */
176
+ pin(): Promise<void>;
177
+ /** Release the pinned slot. */
178
+ unpin(): Promise<void>;
179
+ onAction(handler: (actionId: string) => void | Promise<void>): void;
180
+ onSubmit(handler: (values: Record<string, string | number>) => void | Promise<void>): void;
181
+ onDismiss(handler: (reason: OpenPetsBubbleDismissReason) => void): void;
182
+ }
183
+ /** Options for opening a sandboxed plugin panel (§7.2). Requires `ui:panel`. */
184
+ export interface OpenPetsPanelOptions {
185
+ /** Name of a manifest-declared panel page (`panels` block). */
186
+ panel: string;
187
+ title?: string;
188
+ width?: number;
189
+ height?: number;
190
+ }
191
+ /** Handle to a sandboxed plugin panel window. */
192
+ export interface OpenPetsPanelHandle {
193
+ readonly id: string;
194
+ show(): Promise<void>;
195
+ hide(): Promise<void>;
196
+ /** Host -> panel. Payload must be clone-safe data. */
197
+ postMessage(msg: unknown): Promise<void>;
198
+ /** Panel -> plugin, clone-safe. */
199
+ onMessage(handler: (msg: unknown) => void): void;
200
+ close(): Promise<void>;
201
+ }
202
+ /** Descriptor for a host-owned, display-level delivery surface. Requires `ui:delivery`. */
203
+ export interface OpenPetsDelivery {
204
+ /** Stable, plugin-scoped id (`[A-Za-z0-9._:-]`, 1–96 chars). */
205
+ key: string;
206
+ /** A manifest-declared courier spritesheet (`ctx.assets.sprite(name)`). */
207
+ courier: OpenPetsAssetRef;
208
+ /** Plain-text title (1–160 characters). */
209
+ title: string;
210
+ /** Optional plain-text detail (0–200 characters). */
211
+ detail: string;
212
+ /** Finite epoch milliseconds, in the next seven days. */
213
+ expiresAt: number;
214
+ }
215
+ export type OpenPetsDeliveryDismissReason = "click" | "manual" | "expired" | "plugin-stopped";
216
+ /** Opaque handle for a host-owned delivery surface. */
217
+ export interface OpenPetsDeliveryHandle {
218
+ dismiss(): Promise<void>;
219
+ onDismiss(handler: (reason: OpenPetsDeliveryDismissReason) => void): void;
220
+ }
221
+ /** Alert header indicator rendered above alert text. Custom art must be a manifest-declared asset. */
222
+ export interface OpenPetsAlertIndicator {
223
+ /** Visible/accessibility label shown next to the icon. */
224
+ label?: string;
225
+ /** Named host icon or manifest-declared icon asset (`ctx.assets.icon(...)`). */
226
+ icon?: OpenPetsIconRef;
227
+ /** Manifest-declared sanitized SVG asset (`ctx.assets.svg(...)`). */
228
+ svg?: OpenPetsAssetRef;
229
+ /** Manifest-declared raster/icon image asset. */
230
+ image?: OpenPetsAssetRef;
231
+ tone?: OpenPetsStatusTone;
232
+ /** Safe CSS color for the icon/SVG (`#hex`, `rgb(a)`, or `hsl(a)`). */
233
+ color?: string;
234
+ /** Safe CSS background color for the circular icon well. */
235
+ background?: string;
236
+ /** Alias for `background`. */
237
+ backgroundColor?: string;
238
+ /** Safe CSS border color for the circular icon well. */
239
+ borderColor?: string;
240
+ }
241
+ /**
242
+ * Must-not-miss pet alert. Alerts render as sticky high-priority bubbles and
243
+ * may also request best-effort sound and OS notification delivery.
244
+ */
245
+ export interface OpenPetsAlert extends Omit<OpenPetsBubble, "sticky" | "priority"> {
246
+ /** Optional sound to play with the alert. Requires `audio` only when set. */
247
+ sound?: OpenPetsSoundRef;
248
+ /** Optional OS notification. Requires `notify` only when set. */
249
+ notify?: {
250
+ title: string;
251
+ body?: string;
252
+ sound?: boolean;
253
+ };
254
+ }
255
+ /** Handle to a live alert bubble. */
256
+ export interface OpenPetsAlertHandle extends OpenPetsBubbleHandle {
257
+ /** Mark the alert acknowledged and dismiss it. */
258
+ acknowledge(): Promise<void>;
259
+ }
260
+ /** Bubbles, toasts, panels, and the dynamic context-menu section. */
261
+ export interface OpenPetsUiApi {
262
+ /** Show a bubble on the default pet. Requires `pet:speak`. */
263
+ bubble(spec: string | OpenPetsBubble): Promise<OpenPetsBubbleHandle>;
264
+ /** Show a transient host toast. Requires `ui:toast`. */
265
+ toast(spec: {
266
+ text: string;
267
+ tone?: OpenPetsStatusTone;
268
+ durationMs?: number;
269
+ }): Promise<void>;
270
+ /** Show a sticky high-priority alert bubble. Requires `pet:speak`. */
271
+ alert(spec: OpenPetsAlert): Promise<OpenPetsAlertHandle>;
272
+ /** Open a sandboxed plugin webview panel. Requires `ui:panel`. */
273
+ panel(spec: OpenPetsPanelOptions): Promise<OpenPetsPanelHandle>;
274
+ /** Show a host-owned delivery surface on the cursor display. Requires `ui:delivery`. */
275
+ delivery(spec: OpenPetsDelivery): Promise<OpenPetsDeliveryHandle>;
276
+ /** Fully dynamic context-menu section. Requires `commands`. */
277
+ menu: OpenPetsMenuApi;
278
+ }
279
+ /** OS notifications. Requires `notify`. */
280
+ export interface OpenPetsNotifyApi {
281
+ notify(spec: {
282
+ title: string;
283
+ body?: string;
284
+ sound?: boolean;
285
+ }): Promise<void>;
286
+ }
287
+ /** Play named host sounds or bundled plugin sounds. Requires `audio`. */
288
+ export interface OpenPetsAudioApi {
289
+ /**
290
+ * Play a sound. Named host sounds (`"chime"`, `"pop"`, `"nom"`, `"alert"`,
291
+ * `"level-up"`) need no assets; bundled sounds come from
292
+ * {@link OpenPetsAssetsApi.sound}. Gated by the global sound toggle and
293
+ * quiet hours.
294
+ */
295
+ play(sound: OpenPetsSoundRef, options?: {
296
+ volume?: number;
297
+ }): Promise<void>;
298
+ /** Import a user-picked sound into plugin-owned host storage. Requires `audio` and `files`. */
299
+ importUserSound(file: OpenPetsPickedFile, opts?: {
300
+ name?: string;
301
+ }): Promise<OpenPetsUserSoundRef>;
302
+ /** Forget a previously imported user sound. Requires `audio`. */
303
+ forgetUserSound(ref: OpenPetsUserSoundRef): Promise<void>;
304
+ stop(handle?: string): Promise<void>;
305
+ }
306
+ /** Curated, read-only host event names. */
307
+ export type OpenPetsEventName = "pet:clicked" | "pet:doubleClicked" | "pet:dragStart" | "pet:dragEnd" | "pet:hover" | "pet:drop" | "idle:enter" | "idle:exit" | "agent:activity" | "config:changed" | "screen:locked" | "screen:unlocked" | "power:battery-low" | "power:charging" | "display:changed" | "online" | "offline" | "day:partChanged";
308
+ /** A file delivered by an explicit user drop onto the pet (§13.7). */
309
+ export interface OpenPetsDroppedFile {
310
+ name: string;
311
+ sizeBytes: number;
312
+ /** One-shot, size-capped read of the dropped file's text content. */
313
+ readText(): Promise<string>;
314
+ }
315
+ /** Payload for `pet:drop`. */
316
+ export interface OpenPetsDropEvent {
317
+ kind: "text" | "files";
318
+ text?: string;
319
+ files?: OpenPetsDroppedFile[];
320
+ petId: string;
321
+ }
322
+ /** Payload shapes per event. Unlisted events carry a small data record. */
323
+ export interface OpenPetsEventPayloads {
324
+ "pet:clicked": {
325
+ petId: string;
326
+ };
327
+ "pet:doubleClicked": {
328
+ petId: string;
329
+ };
330
+ "pet:dragStart": {
331
+ petId: string;
332
+ };
333
+ "pet:dragEnd": {
334
+ petId: string;
335
+ };
336
+ "pet:hover": {
337
+ petId: string;
338
+ };
339
+ "pet:drop": OpenPetsDropEvent;
340
+ "idle:enter": {
341
+ idleSeconds: number;
342
+ };
343
+ "idle:exit": {
344
+ idleSeconds: number;
345
+ };
346
+ "agent:activity": {
347
+ kind: string;
348
+ reaction?: string;
349
+ active: boolean;
350
+ petId: string;
351
+ };
352
+ "config:changed": Record<string, unknown>;
353
+ "screen:locked": Record<string, never>;
354
+ "screen:unlocked": Record<string, never>;
355
+ "power:battery-low": {
356
+ percent?: number;
357
+ };
358
+ "power:charging": {
359
+ charging: boolean;
360
+ };
361
+ "display:changed": {
362
+ displays: number;
363
+ };
364
+ online: Record<string, never>;
365
+ offline: Record<string, never>;
366
+ "day:partChanged": {
367
+ part: "morning" | "afternoon" | "evening" | "night";
368
+ };
369
+ }
370
+ export type OpenPetsEvent<E extends OpenPetsEventName> = OpenPetsEventPayloads[E];
371
+ /**
372
+ * Subscribe to the curated host event stream. Requires `events`
373
+ * (`pet:drop` additionally requires `pet:drop`).
374
+ *
375
+ * The event set is explicitly bounded: no keystrokes, no screen contents, no
376
+ * other apps' window titles, no clipboard, no ambient filesystem watching.
377
+ */
378
+ export interface OpenPetsEventsApi {
379
+ on<E extends OpenPetsEventName>(event: E, handler: (e: OpenPetsEvent<E>) => void): () => void;
380
+ }
381
+ /** Options for {@link OpenPetsPetHandle.moveBy}. */
382
+ export interface OpenPetsMoveByOptions {
383
+ x: number;
384
+ y: number;
385
+ durationMs?: number;
386
+ }
387
+ /** Options for {@link OpenPetsPetHandle.wander}. */
388
+ export interface OpenPetsWanderOptions {
389
+ distance?: number;
390
+ durationMs?: number;
391
+ }
392
+ /** Beyond named reactions: a bundled spritesheet animation (§5). */
393
+ export type OpenPetsAnimationState = OpenPetsReaction | {
394
+ sprite: OpenPetsAssetRef;
395
+ loop?: boolean;
396
+ fps?: number;
397
+ };
398
+ /** Self-perception snapshot returned by {@link OpenPetsPetHandle.getState}. */
399
+ export interface OpenPetsPetState {
400
+ position: OpenPetsPoint;
401
+ bounds: OpenPetsRect;
402
+ currentAnimation: string;
403
+ visible: boolean;
404
+ dragging: boolean;
405
+ }
406
+ export interface OpenPetsPetInfo {
407
+ id: string;
408
+ name: string;
409
+ kind: "default" | "agent" | "plugin";
410
+ visible: boolean;
411
+ }
412
+ export interface OpenPetsReactOptions {
413
+ /** Set false to animate without showing the built-in reaction/status message. */
414
+ showMessage?: boolean;
415
+ }
416
+ /** Addressable handle to a single pet. */
417
+ export interface OpenPetsPetHandle {
418
+ readonly id: string;
419
+ /** Requires `pet:speak`. Accepts plain text or a full bubble descriptor. */
420
+ speak(spec: string | OpenPetsBubble): Promise<OpenPetsBubbleHandle>;
421
+ /** Requires `pet:reaction`. */
422
+ react(reaction: OpenPetsReaction, options?: OpenPetsReactOptions): Promise<void>;
423
+ /** Requires `pet:animate` for sprite states; named reactions need `pet:reaction`. */
424
+ setAnimation(state: OpenPetsAnimationState): Promise<void>;
425
+ /** Bounded by the host (0.5–2). Requires `pet:animate`. */
426
+ setScale(scale: number): Promise<void>;
427
+ /** Show a status reaction (or clear with null). Requires `pet:reaction`. */
428
+ setStatusReaction(reaction: OpenPetsReaction | null): Promise<void>;
429
+ /** Requires `pet:move`. Movement is bounded to the work area. */
430
+ moveBy(options: OpenPetsMoveByOptions): Promise<void>;
431
+ /** Requires `pet:move`. */
432
+ wander(options?: OpenPetsWanderOptions): Promise<void>;
433
+ /** Requires `pet:move`. */
434
+ moveToHome(): Promise<void>;
435
+ /** Animated absolute move. Requires `pet:move`. */
436
+ moveTo(point: OpenPetsPoint, opts?: {
437
+ durationMs?: number;
438
+ easing?: string;
439
+ }): Promise<void>;
440
+ /** Continuous cursor following with lag. Requires `pet:move`. */
441
+ followCursor(opts?: {
442
+ enabled: boolean;
443
+ lag?: number;
444
+ }): Promise<void>;
445
+ /** Lightweight physics (gravity/bounce). Requires `pet:move`. */
446
+ physics(opts?: {
447
+ gravity?: boolean;
448
+ bounce?: number;
449
+ climbEdges?: boolean;
450
+ }): Promise<void>;
451
+ /**
452
+ * Continuous behavior loop — host-driven, throttled, auto-paused while the
453
+ * pet is hidden or being dragged. The brain loop for "alive" pets.
454
+ * Requires `events`. Returns an unsubscribe function.
455
+ */
456
+ onTick(handler: (dtMs: number) => void): () => void;
457
+ /** Self-perception. Requires `pets:read`. */
458
+ getState(): Promise<OpenPetsPetState>;
459
+ /** Requires `pets:manage`. */
460
+ show(): Promise<void>;
461
+ /** Requires `pets:manage`. */
462
+ hide(): Promise<void>;
463
+ /** Close an ephemeral/spawned pet. Requires `pets:manage`. */
464
+ close(): Promise<void>;
465
+ }
466
+ /** Multi-pet registry: list, target, spawn. */
467
+ export interface OpenPetsPetsApi {
468
+ readonly default: OpenPetsPetHandle;
469
+ /** Requires `pets:read`. */
470
+ list(): Promise<OpenPetsPetInfo[]>;
471
+ get(petId: string): OpenPetsPetHandle;
472
+ /** Spawn an ephemeral pet window. Requires `pets:manage`. */
473
+ spawn(spec: {
474
+ petId: string;
475
+ name?: string;
476
+ position?: OpenPetsPoint;
477
+ ephemeral?: boolean;
478
+ }): Promise<OpenPetsPetHandle>;
479
+ /** Requires `pets:read`. Returns an unsubscribe function. */
480
+ onChange(handler: (pets: OpenPetsPetInfo[]) => void): () => void;
481
+ }
482
+ /** A single field in a command's input form (extended in v3, §8.1). */
37
483
  export interface OpenPetsCommandFormField {
38
484
  /** Identifier for this field; the submitted value is keyed by it. */
39
485
  id: string;
40
- type: "text" | "textarea" | "number";
486
+ type: "text" | "textarea" | "number" | "boolean" | "select" | "multiselect" | "time" | "date" | "list";
41
487
  label: string;
42
- default?: string | number;
488
+ default?: string | number | boolean | string[];
489
+ options?: Array<{
490
+ label: string;
491
+ value: string;
492
+ }>;
43
493
  /** Minimum value (number fields only). */
44
494
  min?: number;
45
495
  /** Maximum value (number fields only). */
@@ -59,55 +509,48 @@ export interface OpenPetsCommand {
59
509
  id: string;
60
510
  title: string;
61
511
  description?: string;
512
+ icon?: OpenPetsIconRef;
62
513
  /** When present, the host opens a dialog and passes validated values to the handler. */
63
514
  form?: OpenPetsCommandForm;
515
+ /** Top-level item vs grouped plugin submenu. Default "submenu". */
516
+ placement?: "top" | "submenu";
517
+ /** Ordering within its group (higher first). */
518
+ priority?: number;
519
+ featured?: boolean;
520
+ /** Maximum time the host waits for this command (1 second to 5 minutes). Defaults to 5 seconds. */
521
+ timeoutMs?: number;
522
+ }
523
+ /** A dynamic context-menu item set via {@link OpenPetsMenuApi.setItems}. */
524
+ export interface OpenPetsMenuItem {
525
+ id: string;
526
+ title: string;
527
+ enabled?: boolean;
528
+ checked?: boolean;
529
+ }
530
+ /** Fully dynamic context-menu section. Requires `commands`. */
531
+ export interface OpenPetsMenuApi {
532
+ setItems(items: OpenPetsMenuItem[]): Promise<void>;
533
+ onSelect(handler: (id: string) => void | Promise<void>): () => void;
534
+ }
535
+ /** Handler invoked when a command runs, with validated form values when present. */
536
+ export type OpenPetsCommandHandler = (values?: Record<string, unknown>) => void | Promise<void>;
537
+ /** Register right-click pet actions. Requires `commands`. */
538
+ export interface OpenPetsCommandsApi {
539
+ register(command: OpenPetsCommand, handler: OpenPetsCommandHandler): Promise<void>;
540
+ unregister(id: string): Promise<void>;
64
541
  }
65
542
  /** A daily schedule time, optionally restricted to specific weekdays. */
66
543
  export type OpenPetsDailySpec = string | {
67
544
  time: string;
68
545
  days?: number[];
69
546
  };
70
- /** Options for {@link OpenPetsPetApi.moveBy}. */
71
- export interface OpenPetsMoveByOptions {
72
- x: number;
73
- y: number;
74
- durationMs?: number;
75
- }
76
- /** Options for {@link OpenPetsPetApi.wander}. */
77
- export interface OpenPetsWanderOptions {
78
- distance?: number;
79
- durationMs?: number;
80
- }
81
- /** Options for {@link OpenPetsHttpApi.fetch}. GET-only by design. */
82
- export interface OpenPetsHttpOptions {
83
- method?: "GET";
84
- headers?: Record<string, string>;
85
- timeoutMs?: number;
86
- }
87
- /** Response returned by {@link OpenPetsHttpApi.fetch}. */
88
- export interface OpenPetsHttpResponse {
89
- status: number;
90
- ok: boolean;
91
- headers: Record<string, string>;
92
- text: string;
93
- json?: unknown;
94
- }
95
- /** Make the default pet speak, react, or move. */
96
- export interface OpenPetsPetApi {
97
- /** Requires `pet:speak`. Messages are capped at 140 chars and content-filtered. */
98
- speak(message: string): Promise<void>;
99
- /** Requires `pet:reaction`. */
100
- react(reaction: OpenPetsReaction): Promise<void>;
101
- /** Requires `pet:move`. Movement is bounded to the work area. */
102
- moveBy(options: OpenPetsMoveByOptions): Promise<void>;
103
- /** Requires `pet:move`. */
104
- wander(options?: OpenPetsWanderOptions): Promise<void>;
105
- /** Requires `pet:move`. */
106
- moveToHome(): Promise<void>;
107
- }
108
547
  /** A callback fired by a schedule. */
109
548
  export type OpenPetsScheduleHandler = () => void | Promise<void>;
110
- /** Run callbacks once, on an interval, or daily. Requires `schedule`. */
549
+ /**
550
+ * Run callbacks once, on an interval, daily, on a cron expression, or at an
551
+ * absolute time. Wall-clock aware: schedules re-arm after sleep/wake.
552
+ * Requires `schedule`.
553
+ */
111
554
  export interface OpenPetsScheduleApi {
112
555
  /** Fire once after `delayMs`. */
113
556
  once(id: string, delayMs: number, handler: OpenPetsScheduleHandler): Promise<void>;
@@ -115,14 +558,25 @@ export interface OpenPetsScheduleApi {
115
558
  every(id: string, intervalMs: number, handler: OpenPetsScheduleHandler): Promise<void>;
116
559
  /** Fire daily at `HH:mm`, optionally only on the given weekdays (0–6, Sunday = 0). */
117
560
  daily(id: string, spec: OpenPetsDailySpec, handler: OpenPetsScheduleHandler): Promise<void>;
561
+ /** Fire on a 5-field cron expression (`m h dom mon dow`). */
562
+ cron(id: string, expr: string, handler: OpenPetsScheduleHandler): Promise<void>;
563
+ /** Fire once at an absolute ISO-8601 timestamp. Past timestamps fire immediately. */
564
+ at(id: string, isoTimestamp: string, handler: OpenPetsScheduleHandler): Promise<void>;
565
+ list(): Promise<Array<{
566
+ id: string;
567
+ nextRunMs: number;
568
+ }>>;
118
569
  cancel(id: string): Promise<void>;
119
570
  cancelAll(): Promise<void>;
120
571
  }
121
- /** Persist small per-plugin state across restarts. Requires `storage`. */
572
+ /** Persist per-plugin state across restarts (quota: a few MB). Requires `storage`. */
122
573
  export interface OpenPetsStorageApi {
123
574
  get<T = unknown>(key: string): Promise<T | undefined>;
124
575
  set(key: string, value: unknown): Promise<void>;
125
576
  delete(key: string): Promise<void>;
577
+ keys(): Promise<string[]>;
578
+ /** Reactive subscription to a key. Returns an unsubscribe function. */
579
+ subscribe(key: string, handler: (value: unknown) => void): () => void;
126
580
  }
127
581
  /** Read user configuration and react to changes. Always available. */
128
582
  export interface OpenPetsConfigApi {
@@ -130,22 +584,200 @@ export interface OpenPetsConfigApi {
130
584
  /** Subscribe to config changes. Returns an unsubscribe function. */
131
585
  onChange<T = Record<string, unknown>>(handler: (config: T) => void | Promise<void>): () => void;
132
586
  }
133
- /** Handler invoked when a command runs, with validated form values when present. */
134
- export type OpenPetsCommandHandler = (values?: Record<string, unknown>) => void | Promise<void>;
135
- /** Register right-click pet actions. Requires `commands`. */
136
- export interface OpenPetsCommandsApi {
137
- register(command: OpenPetsCommand, handler: OpenPetsCommandHandler): Promise<void>;
138
- unregister(id: string): Promise<void>;
587
+ /** Inter-plugin pub/sub. Topics should be namespaced `pluginId/topic`. Requires `bus`. */
588
+ export interface OpenPetsBusApi {
589
+ /** Publish a clone-safe payload. Rate- and size-capped by the host. */
590
+ publish(topic: string, payload: unknown): Promise<void>;
591
+ subscribe(topic: string, handler: (payload: unknown) => void): () => void;
592
+ }
593
+ /** Options for {@link OpenPetsNetApi.fetch}. Non-GET methods require `network:write`. */
594
+ export interface OpenPetsNetFetchOptions {
595
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
596
+ headers?: Record<string, string>;
597
+ body?: string;
598
+ timeoutMs?: number;
599
+ }
600
+ /** Response returned by {@link OpenPetsNetApi.fetch}. */
601
+ export interface OpenPetsHttpResponse {
602
+ status: number;
603
+ ok: boolean;
604
+ headers: Record<string, string>;
605
+ text: string;
606
+ json?: unknown;
607
+ }
608
+ /**
609
+ * HTTPS fetch to manifest-declared, user-approved hosts. Requires `network`.
610
+ * HTTPS-only with redirect, response-size, and private-IP/SSRF guards.
611
+ */
612
+ export interface OpenPetsNetApi {
613
+ fetch(url: string, options?: OpenPetsNetFetchOptions): Promise<OpenPetsHttpResponse>;
614
+ /** Streaming fetch (SSE/chunked) for LLM-style responses. */
615
+ stream(url: string, options: OpenPetsNetFetchOptions, onChunk: (chunk: string) => void): Promise<{
616
+ status: number;
617
+ ok: boolean;
618
+ }>;
619
+ }
620
+ /** v2 GET-only fetch options, kept for back-compat (`ctx.http`). */
621
+ export interface OpenPetsHttpOptions {
622
+ method?: "GET";
623
+ headers?: Record<string, string>;
624
+ timeoutMs?: number;
625
+ }
626
+ /** v2 HTTP namespace, kept for back-compat. Requires `network`. */
627
+ export interface OpenPetsHttpApi {
628
+ fetch(url: string, options?: OpenPetsHttpOptions): Promise<OpenPetsHttpResponse>;
629
+ }
630
+ /** A tool the model may call (host AI gateway function calling). */
631
+ export interface OpenPetsAiTool {
632
+ name: string;
633
+ description?: string;
634
+ /** JSON Schema for the tool's input. */
635
+ inputSchema: Record<string, unknown>;
636
+ }
637
+ /** A tool call requested by the model. */
638
+ export interface OpenPetsAiToolCall {
639
+ name: string;
640
+ input: Record<string, unknown>;
641
+ }
642
+ /** Request for {@link OpenPetsAiApi.complete} / {@link OpenPetsAiApi.stream}. */
643
+ export interface OpenPetsAiRequest {
644
+ system?: string;
645
+ messages: Array<{
646
+ role: "user" | "assistant";
647
+ content: string;
648
+ }>;
649
+ maxTokens?: number;
650
+ temperature?: number;
651
+ tools?: OpenPetsAiTool[];
652
+ }
653
+ /**
654
+ * Host AI gateway backed by the user's one configured provider/model.
655
+ * Keys live in host config, never in plugin code. Requires `ai`.
656
+ */
657
+ export interface OpenPetsAiApi {
658
+ /** Whether a provider is configured. */
659
+ available(): Promise<boolean>;
660
+ complete(req: OpenPetsAiRequest): Promise<{
661
+ text: string;
662
+ toolCalls?: OpenPetsAiToolCall[];
663
+ }>;
664
+ stream(req: OpenPetsAiRequest, onToken: (chunk: string) => void): Promise<{
665
+ text: string;
666
+ }>;
667
+ }
668
+ /** Encrypted per-plugin credentials (OS keychain / safeStorage). Requires `secrets`. */
669
+ export interface OpenPetsSecretsApi {
670
+ set(key: string, value: string): Promise<void>;
671
+ /** Returned only to the owning plugin. */
672
+ get(key: string): Promise<string | undefined>;
673
+ delete(key: string): Promise<void>;
674
+ has(key: string): Promise<boolean>;
675
+ }
676
+ /** TTS out (`voice:speak`) and opt-in, push-to-talk STT in (`voice:listen`). */
677
+ export interface OpenPetsVoiceApi {
678
+ speak(text: string, opts?: {
679
+ voice?: string;
680
+ rate?: number;
681
+ }): Promise<void>;
682
+ /**
683
+ * One-shot speech-to-text. Off by default, never ambient, clearly
684
+ * indicated. Requires `voice:listen` (sensitive) and a configured
685
+ * transcription-capable AI provider.
686
+ */
687
+ listen(opts?: {
688
+ timeoutMs?: number;
689
+ }): Promise<{
690
+ text: string;
691
+ }>;
692
+ }
693
+ /** Host-mediated OAuth configuration for the host-approved provider registry. */
694
+ export interface OpenPetsOauthConfig {
695
+ provider: "google" | "spotify";
696
+ clientId: string;
697
+ /** Optional installed-app credential secret required by the provider token endpoint. */
698
+ clientSecret?: string;
699
+ scopes: string[];
700
+ }
701
+ /** Tokens returned by the host after running the OAuth dance. */
702
+ export interface OpenPetsOauthTokens {
703
+ accessToken: string;
704
+ refreshToken?: string;
705
+ expiresAt?: number;
706
+ }
707
+ /** Stable OAuth error codes returned by host-managed providers. */
708
+ export type OpenPetsOauthErrorCode = "invalid_grant";
709
+ /** OAuth errors may expose this code for reconnect-required handling. */
710
+ export interface OpenPetsOauthError extends Error {
711
+ readonly code: OpenPetsOauthErrorCode;
712
+ }
713
+ /** Host-mediated OAuth: the host opens the system browser and runs PKCE. Requires `auth`. */
714
+ export interface OpenPetsAuthApi {
715
+ oauth(config: OpenPetsOauthConfig): Promise<OpenPetsOauthTokens>;
716
+ refresh(provider: string): Promise<{
717
+ accessToken: string;
718
+ expiresAt?: number;
719
+ }>;
720
+ signOut(provider: string): Promise<void>;
721
+ }
722
+ /** A user-picked file. Reads are size-capped; no plugin-chosen paths. */
723
+ export interface OpenPetsPickedFile {
724
+ name: string;
725
+ sizeBytes: number;
726
+ readText(): Promise<string>;
727
+ readBytes(): Promise<Uint8Array>;
728
+ }
729
+ /** User-initiated file access behind OS dialogs. Requires `files`. */
730
+ export interface OpenPetsFilesApi {
731
+ pick(opts?: {
732
+ accept?: string[];
733
+ multiple?: boolean;
734
+ }): Promise<OpenPetsPickedFile[]>;
735
+ save(opts: {
736
+ suggestedName: string;
737
+ data: string | Uint8Array;
738
+ }): Promise<void>;
739
+ }
740
+ /** Read-only environment info. Always non-sensitive. */
741
+ export interface OpenPetsSystemInfo {
742
+ platform: "mac" | "win" | "linux";
743
+ locale: string;
744
+ timezone: string;
745
+ theme: "light" | "dark";
746
+ appVersion: string;
747
+ online: boolean;
748
+ }
749
+ /** Aggregate machine metrics — never per-process or per-app data. */
750
+ export interface OpenPetsSystemMetrics {
751
+ /** 0–100, recent average. */
752
+ cpuPercent: number;
753
+ /** 0–100. */
754
+ memUsedPercent: number;
755
+ battery?: {
756
+ percent: number;
757
+ charging: boolean;
758
+ };
759
+ }
760
+ /** Read-only environment + open-external + gated clipboard. */
761
+ export interface OpenPetsSystemApi {
762
+ /** Always available. */
763
+ info(): Promise<OpenPetsSystemInfo>;
764
+ /** Requires `system:metrics`. */
765
+ metrics(): Promise<OpenPetsSystemMetrics>;
766
+ /** HTTPS-only; opens the user's real browser. Requires `system:openExternal`. */
767
+ openExternal(url: string): Promise<void>;
768
+ /**
769
+ * Allowed only from inside a user-invoked command handler, never ambient.
770
+ * Requires `clipboard` (sensitive).
771
+ */
772
+ readClipboardText(): Promise<string>;
773
+ /** Requires `clipboard`. */
774
+ writeClipboardText(text: string): Promise<void>;
139
775
  }
140
776
  /** Show a short status line in the Plugins window. Requires `status`. */
141
777
  export interface OpenPetsStatusApi {
142
778
  set(status: OpenPetsStatus): Promise<void>;
143
779
  clear(): Promise<void>;
144
780
  }
145
- /** Fetch approved HTTPS hosts through the OpenPets proxy. Requires `network`. */
146
- export interface OpenPetsHttpApi {
147
- fetch(url: string, options?: OpenPetsHttpOptions): Promise<OpenPetsHttpResponse>;
148
- }
149
781
  /** Write to the desktop app log under the `plugin` scope. Always available. */
150
782
  export interface OpenPetsLogApi {
151
783
  debug(...args: unknown[]): Promise<void>;
@@ -157,23 +789,67 @@ export interface OpenPetsLogApi {
157
789
  * The capability object passed to {@link OpenPetsPluginDefinition.start}.
158
790
  *
159
791
  * Each namespace is only functional when the matching permission is approved
160
- * in the manifest. `config` and `log` are always available.
792
+ * in the manifest. `config`, `log`, `assets`, and `system.info` are always
793
+ * available.
794
+ *
795
+ * `pet` is an alias for `pets.default` (back-compat + convenience); the v2
796
+ * surface (`ctx.pet.speak(text)`, `ctx.http.fetch`) keeps working.
161
797
  */
162
798
  export interface OpenPetsContext {
163
- pet: OpenPetsPetApi;
799
+ pets: OpenPetsPetsApi;
800
+ pet: OpenPetsPetHandle;
801
+ ui: OpenPetsUiApi;
802
+ audio: OpenPetsAudioApi;
803
+ events: OpenPetsEventsApi;
804
+ assets: OpenPetsAssetsApi;
805
+ bus: OpenPetsBusApi;
164
806
  schedule: OpenPetsScheduleApi;
165
807
  storage: OpenPetsStorageApi;
166
808
  config: OpenPetsConfigApi;
809
+ net: OpenPetsNetApi;
810
+ notify: OpenPetsNotifyApi;
811
+ ai: OpenPetsAiApi;
812
+ secrets: OpenPetsSecretsApi;
813
+ voice: OpenPetsVoiceApi;
814
+ auth: OpenPetsAuthApi;
815
+ files: OpenPetsFilesApi;
816
+ system: OpenPetsSystemApi;
167
817
  commands: OpenPetsCommandsApi;
168
818
  status: OpenPetsStatusApi;
819
+ /** v2 alias of `net` (GET-only). */
169
820
  http: OpenPetsHttpApi;
170
821
  log: OpenPetsLogApi;
822
+ /**
823
+ * Translate a key against the plugin's own `locales/<locale>.json` catalogs
824
+ * for the active host locale, falling back to the plugin's `en` catalog and
825
+ * then to the raw key. `{var}` placeholders are interpolated from `vars`.
826
+ *
827
+ * Use this for strings the plugin *composes at runtime* (bubble/notify/status
828
+ * bodies). Static manifest strings (`name`, `description`, `configSchema`
829
+ * labels, command titles) should instead use the `$t:key` reference form,
830
+ * which the host resolves at display time.
831
+ *
832
+ * ```ts
833
+ * await ctx.ui.bubble({ text: ctx.t("reminder.fire", { message: "Stretch" }) })
834
+ * // locales/ja.json: { "reminder.fire": "リマインダー: {message}" } -> "リマインダー: Stretch"
835
+ * ```
836
+ */
837
+ t: (key: string, vars?: Record<string, string | number>) => string;
838
+ /**
839
+ * The active host locale string (e.g. `"en"`, `"ja"`, `"pt-BR"`, `"zh-Hans"`).
840
+ * Read it to branch on language at runtime; `ctx.t` already follows it.
841
+ *
842
+ * ```ts
843
+ * if (ctx.locale.startsWith("ja")) { /* ... *\/ }
844
+ * ```
845
+ */
846
+ readonly locale: string;
171
847
  }
172
848
  /** The object you pass to {@link OpenPetsPluginApi.register}. */
173
849
  export interface OpenPetsPluginDefinition {
174
850
  /** Runs when the plugin is enabled or the app launches. */
175
851
  start(ctx: OpenPetsContext): void | Promise<void>;
176
- /** Optional cleanup. Schedules and commands are torn down for you. */
852
+ /** Optional cleanup. Schedules, commands, bubbles, and subscriptions are torn down for you. */
177
853
  stop?(ctx?: OpenPetsContext): void | Promise<void>;
178
854
  }
179
855
  /** The injected global used to register a plugin. */
@@ -192,7 +868,11 @@ export interface OpenPetsPluginApi {
192
868
  */
193
869
  export type OpenPetsPluginEntry = (api: OpenPetsPluginApi) => void | Promise<void>;
194
870
  /** Field types supported by `configSchema` in the manifest. */
195
- export type OpenPetsConfigFieldType = "text" | "textarea" | "number" | "boolean" | "select" | "time" | "list" | "multiselect";
871
+ export type OpenPetsConfigFieldType = "text" | "textarea" | "number" | "boolean" | "select" | "time" | "date" | "list" | "multiselect" | "sound"
872
+ /** Masked input, encrypted at rest, never logged or echoed (v3). */
873
+ | "secret";
874
+ /** v2 alias: the old flat pet namespace shape. */
875
+ export type OpenPetsPetApi = Pick<OpenPetsPetHandle, "speak" | "react" | "moveBy" | "wander" | "moveToHome">;
196
876
  declare global {
197
877
  /**
198
878
  * Injected by the OpenPets runtime. Call once at the top level of your