@open-pets/plugin-sdk 2.1.1 → 3.0.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,461 @@
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" | "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
+ /** Alert header indicator rendered above alert text. Custom art must be a manifest-declared asset. */
203
+ export interface OpenPetsAlertIndicator {
204
+ /** Visible/accessibility label shown next to the icon. */
205
+ label?: string;
206
+ /** Named host icon or manifest-declared icon asset (`ctx.assets.icon(...)`). */
207
+ icon?: OpenPetsIconRef;
208
+ /** Manifest-declared sanitized SVG asset (`ctx.assets.svg(...)`). */
209
+ svg?: OpenPetsAssetRef;
210
+ /** Manifest-declared raster/icon image asset. */
211
+ image?: OpenPetsAssetRef;
212
+ tone?: OpenPetsStatusTone;
213
+ /** Safe CSS color for the icon/SVG (`#hex`, `rgb(a)`, or `hsl(a)`). */
214
+ color?: string;
215
+ /** Safe CSS background color for the circular icon well. */
216
+ background?: string;
217
+ /** Alias for `background`. */
218
+ backgroundColor?: string;
219
+ /** Safe CSS border color for the circular icon well. */
220
+ borderColor?: string;
221
+ }
222
+ /**
223
+ * Must-not-miss pet alert. Alerts render as sticky high-priority bubbles and
224
+ * may also request best-effort sound and OS notification delivery.
225
+ */
226
+ export interface OpenPetsAlert extends Omit<OpenPetsBubble, "sticky" | "priority"> {
227
+ /** Optional sound to play with the alert. Requires `audio` only when set. */
228
+ sound?: OpenPetsSoundRef;
229
+ /** Optional OS notification. Requires `notify` only when set. */
230
+ notify?: {
231
+ title: string;
232
+ body?: string;
233
+ sound?: boolean;
234
+ };
235
+ }
236
+ /** Handle to a live alert bubble. */
237
+ export interface OpenPetsAlertHandle extends OpenPetsBubbleHandle {
238
+ /** Mark the alert acknowledged and dismiss it. */
239
+ acknowledge(): Promise<void>;
240
+ }
241
+ /** Bubbles, toasts, panels, and the dynamic context-menu section. */
242
+ export interface OpenPetsUiApi {
243
+ /** Show a bubble on the default pet. Requires `pet:speak`. */
244
+ bubble(spec: string | OpenPetsBubble): Promise<OpenPetsBubbleHandle>;
245
+ /** Show a transient host toast. Requires `ui:toast`. */
246
+ toast(spec: {
247
+ text: string;
248
+ tone?: OpenPetsStatusTone;
249
+ durationMs?: number;
250
+ }): Promise<void>;
251
+ /** Show a sticky high-priority alert bubble. Requires `pet:speak`. */
252
+ alert(spec: OpenPetsAlert): Promise<OpenPetsAlertHandle>;
253
+ /** Open a sandboxed plugin webview panel. Requires `ui:panel`. */
254
+ panel(spec: OpenPetsPanelOptions): Promise<OpenPetsPanelHandle>;
255
+ /** Fully dynamic context-menu section. Requires `commands`. */
256
+ menu: OpenPetsMenuApi;
257
+ }
258
+ /** OS notifications. Requires `notify`. */
259
+ export interface OpenPetsNotifyApi {
260
+ notify(spec: {
261
+ title: string;
262
+ body?: string;
263
+ sound?: boolean;
264
+ }): Promise<void>;
265
+ }
266
+ /** Play named host sounds or bundled plugin sounds. Requires `audio`. */
267
+ export interface OpenPetsAudioApi {
268
+ /**
269
+ * Play a sound. Named host sounds (`"chime"`, `"pop"`, `"nom"`, `"alert"`,
270
+ * `"level-up"`) need no assets; bundled sounds come from
271
+ * {@link OpenPetsAssetsApi.sound}. Gated by the global sound toggle and
272
+ * quiet hours.
273
+ */
274
+ play(sound: OpenPetsSoundRef, options?: {
275
+ volume?: number;
276
+ }): Promise<void>;
277
+ /** Import a user-picked sound into plugin-owned host storage. Requires `audio` and `files`. */
278
+ importUserSound(file: OpenPetsPickedFile, opts?: {
279
+ name?: string;
280
+ }): Promise<OpenPetsUserSoundRef>;
281
+ /** Forget a previously imported user sound. Requires `audio`. */
282
+ forgetUserSound(ref: OpenPetsUserSoundRef): Promise<void>;
283
+ stop(handle?: string): Promise<void>;
284
+ }
285
+ /** Curated, read-only host event names. */
286
+ 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";
287
+ /** A file delivered by an explicit user drop onto the pet (§13.7). */
288
+ export interface OpenPetsDroppedFile {
289
+ name: string;
290
+ sizeBytes: number;
291
+ /** One-shot, size-capped read of the dropped file's text content. */
292
+ readText(): Promise<string>;
293
+ }
294
+ /** Payload for `pet:drop`. */
295
+ export interface OpenPetsDropEvent {
296
+ kind: "text" | "files";
297
+ text?: string;
298
+ files?: OpenPetsDroppedFile[];
299
+ petId: string;
300
+ }
301
+ /** Payload shapes per event. Unlisted events carry a small data record. */
302
+ export interface OpenPetsEventPayloads {
303
+ "pet:clicked": {
304
+ petId: string;
305
+ };
306
+ "pet:doubleClicked": {
307
+ petId: string;
308
+ };
309
+ "pet:dragStart": {
310
+ petId: string;
311
+ };
312
+ "pet:dragEnd": {
313
+ petId: string;
314
+ };
315
+ "pet:hover": {
316
+ petId: string;
317
+ };
318
+ "pet:drop": OpenPetsDropEvent;
319
+ "idle:enter": {
320
+ idleSeconds: number;
321
+ };
322
+ "idle:exit": {
323
+ idleSeconds: number;
324
+ };
325
+ "agent:activity": {
326
+ kind: string;
327
+ reaction?: string;
328
+ };
329
+ "config:changed": Record<string, unknown>;
330
+ "screen:locked": Record<string, never>;
331
+ "screen:unlocked": Record<string, never>;
332
+ "power:battery-low": {
333
+ percent?: number;
334
+ };
335
+ "power:charging": {
336
+ charging: boolean;
337
+ };
338
+ "display:changed": {
339
+ displays: number;
340
+ };
341
+ online: Record<string, never>;
342
+ offline: Record<string, never>;
343
+ "day:partChanged": {
344
+ part: "morning" | "afternoon" | "evening" | "night";
345
+ };
346
+ }
347
+ export type OpenPetsEvent<E extends OpenPetsEventName> = OpenPetsEventPayloads[E];
348
+ /**
349
+ * Subscribe to the curated host event stream. Requires `events`
350
+ * (`pet:drop` additionally requires `pet:drop`).
351
+ *
352
+ * The event set is explicitly bounded: no keystrokes, no screen contents, no
353
+ * other apps' window titles, no clipboard, no ambient filesystem watching.
354
+ */
355
+ export interface OpenPetsEventsApi {
356
+ on<E extends OpenPetsEventName>(event: E, handler: (e: OpenPetsEvent<E>) => void): () => void;
357
+ }
358
+ /** Options for {@link OpenPetsPetHandle.moveBy}. */
359
+ export interface OpenPetsMoveByOptions {
360
+ x: number;
361
+ y: number;
362
+ durationMs?: number;
363
+ }
364
+ /** Options for {@link OpenPetsPetHandle.wander}. */
365
+ export interface OpenPetsWanderOptions {
366
+ distance?: number;
367
+ durationMs?: number;
368
+ }
369
+ /** Beyond named reactions: a bundled spritesheet animation (§5). */
370
+ export type OpenPetsAnimationState = OpenPetsReaction | {
371
+ sprite: OpenPetsAssetRef;
372
+ loop?: boolean;
373
+ fps?: number;
374
+ };
375
+ /** Self-perception snapshot returned by {@link OpenPetsPetHandle.getState}. */
376
+ export interface OpenPetsPetState {
377
+ position: OpenPetsPoint;
378
+ bounds: OpenPetsRect;
379
+ currentAnimation: string;
380
+ visible: boolean;
381
+ dragging: boolean;
382
+ }
383
+ export interface OpenPetsPetInfo {
384
+ id: string;
385
+ name: string;
386
+ kind: "default" | "agent" | "plugin";
387
+ visible: boolean;
388
+ }
389
+ export interface OpenPetsReactOptions {
390
+ /** Set false to animate without showing the built-in reaction/status message. */
391
+ showMessage?: boolean;
392
+ }
393
+ /** Addressable handle to a single pet. */
394
+ export interface OpenPetsPetHandle {
395
+ readonly id: string;
396
+ /** Requires `pet:speak`. Accepts plain text or a full bubble descriptor. */
397
+ speak(spec: string | OpenPetsBubble): Promise<OpenPetsBubbleHandle>;
398
+ /** Requires `pet:reaction`. */
399
+ react(reaction: OpenPetsReaction, options?: OpenPetsReactOptions): Promise<void>;
400
+ /** Requires `pet:animate` for sprite states; named reactions need `pet:reaction`. */
401
+ setAnimation(state: OpenPetsAnimationState): Promise<void>;
402
+ /** Bounded by the host (0.5–2). Requires `pet:animate`. */
403
+ setScale(scale: number): Promise<void>;
404
+ /** Show a status reaction (or clear with null). Requires `pet:reaction`. */
405
+ setStatusReaction(reaction: OpenPetsReaction | null): Promise<void>;
406
+ /** Requires `pet:move`. Movement is bounded to the work area. */
407
+ moveBy(options: OpenPetsMoveByOptions): Promise<void>;
408
+ /** Requires `pet:move`. */
409
+ wander(options?: OpenPetsWanderOptions): Promise<void>;
410
+ /** Requires `pet:move`. */
411
+ moveToHome(): Promise<void>;
412
+ /** Animated absolute move. Requires `pet:move`. */
413
+ moveTo(point: OpenPetsPoint, opts?: {
414
+ durationMs?: number;
415
+ easing?: string;
416
+ }): Promise<void>;
417
+ /** Continuous cursor following with lag. Requires `pet:move`. */
418
+ followCursor(opts?: {
419
+ enabled: boolean;
420
+ lag?: number;
421
+ }): Promise<void>;
422
+ /** Lightweight physics (gravity/bounce). Requires `pet:move`. */
423
+ physics(opts?: {
424
+ gravity?: boolean;
425
+ bounce?: number;
426
+ climbEdges?: boolean;
427
+ }): Promise<void>;
428
+ /**
429
+ * Continuous behavior loop — host-driven, throttled, auto-paused while the
430
+ * pet is hidden or being dragged. The brain loop for "alive" pets.
431
+ * Requires `events`. Returns an unsubscribe function.
432
+ */
433
+ onTick(handler: (dtMs: number) => void): () => void;
434
+ /** Self-perception. Requires `pets:read`. */
435
+ getState(): Promise<OpenPetsPetState>;
436
+ /** Requires `pets:manage`. */
437
+ show(): Promise<void>;
438
+ /** Requires `pets:manage`. */
439
+ hide(): Promise<void>;
440
+ /** Close an ephemeral/spawned pet. Requires `pets:manage`. */
441
+ close(): Promise<void>;
442
+ }
443
+ /** Multi-pet registry: list, target, spawn. */
444
+ export interface OpenPetsPetsApi {
445
+ readonly default: OpenPetsPetHandle;
446
+ /** Requires `pets:read`. */
447
+ list(): Promise<OpenPetsPetInfo[]>;
448
+ get(petId: string): OpenPetsPetHandle;
449
+ /** Spawn an ephemeral pet window. Requires `pets:manage`. */
450
+ spawn(spec: {
451
+ petId: string;
452
+ name?: string;
453
+ position?: OpenPetsPoint;
454
+ ephemeral?: boolean;
455
+ }): Promise<OpenPetsPetHandle>;
456
+ /** Requires `pets:read`. Returns an unsubscribe function. */
457
+ onChange(handler: (pets: OpenPetsPetInfo[]) => void): () => void;
458
+ }
459
+ /** A single field in a command's input form (extended in v3, §8.1). */
37
460
  export interface OpenPetsCommandFormField {
38
461
  /** Identifier for this field; the submitted value is keyed by it. */
39
462
  id: string;
40
- type: "text" | "textarea" | "number";
463
+ type: "text" | "textarea" | "number" | "boolean" | "select" | "multiselect" | "time" | "date" | "list";
41
464
  label: string;
42
- default?: string | number;
465
+ default?: string | number | boolean | string[];
466
+ options?: Array<{
467
+ label: string;
468
+ value: string;
469
+ }>;
43
470
  /** Minimum value (number fields only). */
44
471
  min?: number;
45
472
  /** Maximum value (number fields only). */
@@ -59,55 +486,46 @@ export interface OpenPetsCommand {
59
486
  id: string;
60
487
  title: string;
61
488
  description?: string;
489
+ icon?: OpenPetsIconRef;
62
490
  /** When present, the host opens a dialog and passes validated values to the handler. */
63
491
  form?: OpenPetsCommandForm;
492
+ /** Top-level item vs grouped plugin submenu. Default "submenu". */
493
+ placement?: "top" | "submenu";
494
+ /** Ordering within its group (higher first). */
495
+ priority?: number;
496
+ featured?: boolean;
497
+ }
498
+ /** A dynamic context-menu item set via {@link OpenPetsMenuApi.setItems}. */
499
+ export interface OpenPetsMenuItem {
500
+ id: string;
501
+ title: string;
502
+ enabled?: boolean;
503
+ checked?: boolean;
504
+ }
505
+ /** Fully dynamic context-menu section. Requires `commands`. */
506
+ export interface OpenPetsMenuApi {
507
+ setItems(items: OpenPetsMenuItem[]): Promise<void>;
508
+ onSelect(handler: (id: string) => void | Promise<void>): () => void;
509
+ }
510
+ /** Handler invoked when a command runs, with validated form values when present. */
511
+ export type OpenPetsCommandHandler = (values?: Record<string, unknown>) => void | Promise<void>;
512
+ /** Register right-click pet actions. Requires `commands`. */
513
+ export interface OpenPetsCommandsApi {
514
+ register(command: OpenPetsCommand, handler: OpenPetsCommandHandler): Promise<void>;
515
+ unregister(id: string): Promise<void>;
64
516
  }
65
517
  /** A daily schedule time, optionally restricted to specific weekdays. */
66
518
  export type OpenPetsDailySpec = string | {
67
519
  time: string;
68
520
  days?: number[];
69
521
  };
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
522
  /** A callback fired by a schedule. */
109
523
  export type OpenPetsScheduleHandler = () => void | Promise<void>;
110
- /** Run callbacks once, on an interval, or daily. Requires `schedule`. */
524
+ /**
525
+ * Run callbacks once, on an interval, daily, on a cron expression, or at an
526
+ * absolute time. Wall-clock aware: schedules re-arm after sleep/wake.
527
+ * Requires `schedule`.
528
+ */
111
529
  export interface OpenPetsScheduleApi {
112
530
  /** Fire once after `delayMs`. */
113
531
  once(id: string, delayMs: number, handler: OpenPetsScheduleHandler): Promise<void>;
@@ -115,14 +533,25 @@ export interface OpenPetsScheduleApi {
115
533
  every(id: string, intervalMs: number, handler: OpenPetsScheduleHandler): Promise<void>;
116
534
  /** Fire daily at `HH:mm`, optionally only on the given weekdays (0–6, Sunday = 0). */
117
535
  daily(id: string, spec: OpenPetsDailySpec, handler: OpenPetsScheduleHandler): Promise<void>;
536
+ /** Fire on a 5-field cron expression (`m h dom mon dow`). */
537
+ cron(id: string, expr: string, handler: OpenPetsScheduleHandler): Promise<void>;
538
+ /** Fire once at an absolute ISO-8601 timestamp. Past timestamps fire immediately. */
539
+ at(id: string, isoTimestamp: string, handler: OpenPetsScheduleHandler): Promise<void>;
540
+ list(): Promise<Array<{
541
+ id: string;
542
+ nextRunMs: number;
543
+ }>>;
118
544
  cancel(id: string): Promise<void>;
119
545
  cancelAll(): Promise<void>;
120
546
  }
121
- /** Persist small per-plugin state across restarts. Requires `storage`. */
547
+ /** Persist per-plugin state across restarts (quota: a few MB). Requires `storage`. */
122
548
  export interface OpenPetsStorageApi {
123
549
  get<T = unknown>(key: string): Promise<T | undefined>;
124
550
  set(key: string, value: unknown): Promise<void>;
125
551
  delete(key: string): Promise<void>;
552
+ keys(): Promise<string[]>;
553
+ /** Reactive subscription to a key. Returns an unsubscribe function. */
554
+ subscribe(key: string, handler: (value: unknown) => void): () => void;
126
555
  }
127
556
  /** Read user configuration and react to changes. Always available. */
128
557
  export interface OpenPetsConfigApi {
@@ -130,22 +559,197 @@ export interface OpenPetsConfigApi {
130
559
  /** Subscribe to config changes. Returns an unsubscribe function. */
131
560
  onChange<T = Record<string, unknown>>(handler: (config: T) => void | Promise<void>): () => void;
132
561
  }
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>;
562
+ /** Inter-plugin pub/sub. Topics should be namespaced `pluginId/topic`. Requires `bus`. */
563
+ export interface OpenPetsBusApi {
564
+ /** Publish a clone-safe payload. Rate- and size-capped by the host. */
565
+ publish(topic: string, payload: unknown): Promise<void>;
566
+ subscribe(topic: string, handler: (payload: unknown) => void): () => void;
567
+ }
568
+ /** Options for {@link OpenPetsNetApi.fetch}. Non-GET methods require `network:write`. */
569
+ export interface OpenPetsNetFetchOptions {
570
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
571
+ headers?: Record<string, string>;
572
+ body?: string;
573
+ timeoutMs?: number;
574
+ }
575
+ /** Response returned by {@link OpenPetsNetApi.fetch}. */
576
+ export interface OpenPetsHttpResponse {
577
+ status: number;
578
+ ok: boolean;
579
+ headers: Record<string, string>;
580
+ text: string;
581
+ json?: unknown;
582
+ }
583
+ /**
584
+ * HTTPS fetch to manifest-declared, user-approved hosts. Requires `network`.
585
+ * HTTPS-only with redirect, response-size, and private-IP/SSRF guards.
586
+ */
587
+ export interface OpenPetsNetApi {
588
+ fetch(url: string, options?: OpenPetsNetFetchOptions): Promise<OpenPetsHttpResponse>;
589
+ /** Streaming fetch (SSE/chunked) for LLM-style responses. */
590
+ stream(url: string, options: OpenPetsNetFetchOptions, onChunk: (chunk: string) => void): Promise<{
591
+ status: number;
592
+ ok: boolean;
593
+ }>;
594
+ }
595
+ /** v2 GET-only fetch options, kept for back-compat (`ctx.http`). */
596
+ export interface OpenPetsHttpOptions {
597
+ method?: "GET";
598
+ headers?: Record<string, string>;
599
+ timeoutMs?: number;
600
+ }
601
+ /** v2 HTTP namespace, kept for back-compat. Requires `network`. */
602
+ export interface OpenPetsHttpApi {
603
+ fetch(url: string, options?: OpenPetsHttpOptions): Promise<OpenPetsHttpResponse>;
604
+ }
605
+ /** A tool the model may call (host AI gateway function calling). */
606
+ export interface OpenPetsAiTool {
607
+ name: string;
608
+ description?: string;
609
+ /** JSON Schema for the tool's input. */
610
+ inputSchema: Record<string, unknown>;
611
+ }
612
+ /** A tool call requested by the model. */
613
+ export interface OpenPetsAiToolCall {
614
+ name: string;
615
+ input: Record<string, unknown>;
616
+ }
617
+ /** Request for {@link OpenPetsAiApi.complete} / {@link OpenPetsAiApi.stream}. */
618
+ export interface OpenPetsAiRequest {
619
+ system?: string;
620
+ messages: Array<{
621
+ role: "user" | "assistant";
622
+ content: string;
623
+ }>;
624
+ maxTokens?: number;
625
+ temperature?: number;
626
+ tools?: OpenPetsAiTool[];
627
+ }
628
+ /**
629
+ * Host AI gateway backed by the user's one configured provider/model.
630
+ * Keys live in host config, never in plugin code. Requires `ai`.
631
+ */
632
+ export interface OpenPetsAiApi {
633
+ /** Whether a provider is configured. */
634
+ available(): Promise<boolean>;
635
+ complete(req: OpenPetsAiRequest): Promise<{
636
+ text: string;
637
+ toolCalls?: OpenPetsAiToolCall[];
638
+ }>;
639
+ stream(req: OpenPetsAiRequest, onToken: (chunk: string) => void): Promise<{
640
+ text: string;
641
+ }>;
642
+ }
643
+ /** Encrypted per-plugin credentials (OS keychain / safeStorage). Requires `secrets`. */
644
+ export interface OpenPetsSecretsApi {
645
+ set(key: string, value: string): Promise<void>;
646
+ /** Returned only to the owning plugin. */
647
+ get(key: string): Promise<string | undefined>;
648
+ delete(key: string): Promise<void>;
649
+ has(key: string): Promise<boolean>;
650
+ }
651
+ /** TTS out (`voice:speak`) and opt-in, push-to-talk STT in (`voice:listen`). */
652
+ export interface OpenPetsVoiceApi {
653
+ speak(text: string, opts?: {
654
+ voice?: string;
655
+ rate?: number;
656
+ }): Promise<void>;
657
+ /**
658
+ * One-shot speech-to-text. Off by default, never ambient, clearly
659
+ * indicated. Requires `voice:listen` (sensitive) and a configured
660
+ * transcription-capable AI provider.
661
+ */
662
+ listen(opts?: {
663
+ timeoutMs?: number;
664
+ }): Promise<{
665
+ text: string;
666
+ }>;
667
+ }
668
+ /** Host-mediated OAuth configuration (PKCE by default; no client secret). */
669
+ export interface OpenPetsOauthConfig {
670
+ provider?: "google" | "microsoft" | "github" | "generic";
671
+ authorizationUrl: string;
672
+ tokenUrl: string;
673
+ clientId: string;
674
+ scopes: string[];
675
+ /** Default true. */
676
+ pkce?: boolean;
677
+ redirect?: "loopback" | "appProtocol";
678
+ }
679
+ /** Tokens returned by the host after running the OAuth dance. */
680
+ export interface OpenPetsOauthTokens {
681
+ accessToken: string;
682
+ refreshToken?: string;
683
+ expiresAt?: number;
684
+ }
685
+ /** Host-mediated OAuth: the host opens the system browser and runs PKCE. Requires `auth`. */
686
+ export interface OpenPetsAuthApi {
687
+ oauth(config: OpenPetsOauthConfig): Promise<OpenPetsOauthTokens>;
688
+ refresh(provider: string): Promise<{
689
+ accessToken: string;
690
+ expiresAt?: number;
691
+ }>;
692
+ signOut(provider: string): Promise<void>;
693
+ }
694
+ /** A user-picked file. Reads are size-capped; no plugin-chosen paths. */
695
+ export interface OpenPetsPickedFile {
696
+ name: string;
697
+ sizeBytes: number;
698
+ readText(): Promise<string>;
699
+ readBytes(): Promise<Uint8Array>;
700
+ }
701
+ /** User-initiated file access behind OS dialogs. Requires `files`. */
702
+ export interface OpenPetsFilesApi {
703
+ pick(opts?: {
704
+ accept?: string[];
705
+ multiple?: boolean;
706
+ }): Promise<OpenPetsPickedFile[]>;
707
+ save(opts: {
708
+ suggestedName: string;
709
+ data: string | Uint8Array;
710
+ }): Promise<void>;
711
+ }
712
+ /** Read-only environment info. Always non-sensitive. */
713
+ export interface OpenPetsSystemInfo {
714
+ platform: "mac" | "win" | "linux";
715
+ locale: string;
716
+ timezone: string;
717
+ theme: "light" | "dark";
718
+ appVersion: string;
719
+ online: boolean;
720
+ }
721
+ /** Aggregate machine metrics — never per-process or per-app data. */
722
+ export interface OpenPetsSystemMetrics {
723
+ /** 0–100, recent average. */
724
+ cpuPercent: number;
725
+ /** 0–100. */
726
+ memUsedPercent: number;
727
+ battery?: {
728
+ percent: number;
729
+ charging: boolean;
730
+ };
731
+ }
732
+ /** Read-only environment + open-external + gated clipboard. */
733
+ export interface OpenPetsSystemApi {
734
+ /** Always available. */
735
+ info(): Promise<OpenPetsSystemInfo>;
736
+ /** Requires `system:metrics`. */
737
+ metrics(): Promise<OpenPetsSystemMetrics>;
738
+ /** HTTPS-only; opens the user's real browser. Requires `system:openExternal`. */
739
+ openExternal(url: string): Promise<void>;
740
+ /**
741
+ * Allowed only from inside a user-invoked command handler, never ambient.
742
+ * Requires `clipboard` (sensitive).
743
+ */
744
+ readClipboardText(): Promise<string>;
745
+ /** Requires `clipboard`. */
746
+ writeClipboardText(text: string): Promise<void>;
139
747
  }
140
748
  /** Show a short status line in the Plugins window. Requires `status`. */
141
749
  export interface OpenPetsStatusApi {
142
750
  set(status: OpenPetsStatus): Promise<void>;
143
751
  clear(): Promise<void>;
144
752
  }
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
753
  /** Write to the desktop app log under the `plugin` scope. Always available. */
150
754
  export interface OpenPetsLogApi {
151
755
  debug(...args: unknown[]): Promise<void>;
@@ -157,23 +761,67 @@ export interface OpenPetsLogApi {
157
761
  * The capability object passed to {@link OpenPetsPluginDefinition.start}.
158
762
  *
159
763
  * Each namespace is only functional when the matching permission is approved
160
- * in the manifest. `config` and `log` are always available.
764
+ * in the manifest. `config`, `log`, `assets`, and `system.info` are always
765
+ * available.
766
+ *
767
+ * `pet` is an alias for `pets.default` (back-compat + convenience); the v2
768
+ * surface (`ctx.pet.speak(text)`, `ctx.http.fetch`) keeps working.
161
769
  */
162
770
  export interface OpenPetsContext {
163
- pet: OpenPetsPetApi;
771
+ pets: OpenPetsPetsApi;
772
+ pet: OpenPetsPetHandle;
773
+ ui: OpenPetsUiApi;
774
+ audio: OpenPetsAudioApi;
775
+ events: OpenPetsEventsApi;
776
+ assets: OpenPetsAssetsApi;
777
+ bus: OpenPetsBusApi;
164
778
  schedule: OpenPetsScheduleApi;
165
779
  storage: OpenPetsStorageApi;
166
780
  config: OpenPetsConfigApi;
781
+ net: OpenPetsNetApi;
782
+ notify: OpenPetsNotifyApi;
783
+ ai: OpenPetsAiApi;
784
+ secrets: OpenPetsSecretsApi;
785
+ voice: OpenPetsVoiceApi;
786
+ auth: OpenPetsAuthApi;
787
+ files: OpenPetsFilesApi;
788
+ system: OpenPetsSystemApi;
167
789
  commands: OpenPetsCommandsApi;
168
790
  status: OpenPetsStatusApi;
791
+ /** v2 alias of `net` (GET-only). */
169
792
  http: OpenPetsHttpApi;
170
793
  log: OpenPetsLogApi;
794
+ /**
795
+ * Translate a key against the plugin's own `locales/<locale>.json` catalogs
796
+ * for the active host locale, falling back to the plugin's `en` catalog and
797
+ * then to the raw key. `{var}` placeholders are interpolated from `vars`.
798
+ *
799
+ * Use this for strings the plugin *composes at runtime* (bubble/notify/status
800
+ * bodies). Static manifest strings (`name`, `description`, `configSchema`
801
+ * labels, command titles) should instead use the `$t:key` reference form,
802
+ * which the host resolves at display time.
803
+ *
804
+ * ```ts
805
+ * await ctx.ui.bubble({ text: ctx.t("reminder.fire", { message: "Stretch" }) })
806
+ * // locales/ja.json: { "reminder.fire": "リマインダー: {message}" } -> "リマインダー: Stretch"
807
+ * ```
808
+ */
809
+ t: (key: string, vars?: Record<string, string | number>) => string;
810
+ /**
811
+ * The active host locale string (e.g. `"en"`, `"ja"`, `"pt-BR"`, `"zh-Hans"`).
812
+ * Read it to branch on language at runtime; `ctx.t` already follows it.
813
+ *
814
+ * ```ts
815
+ * if (ctx.locale.startsWith("ja")) { /* ... *\/ }
816
+ * ```
817
+ */
818
+ readonly locale: string;
171
819
  }
172
820
  /** The object you pass to {@link OpenPetsPluginApi.register}. */
173
821
  export interface OpenPetsPluginDefinition {
174
822
  /** Runs when the plugin is enabled or the app launches. */
175
823
  start(ctx: OpenPetsContext): void | Promise<void>;
176
- /** Optional cleanup. Schedules and commands are torn down for you. */
824
+ /** Optional cleanup. Schedules, commands, bubbles, and subscriptions are torn down for you. */
177
825
  stop?(ctx?: OpenPetsContext): void | Promise<void>;
178
826
  }
179
827
  /** The injected global used to register a plugin. */
@@ -192,7 +840,11 @@ export interface OpenPetsPluginApi {
192
840
  */
193
841
  export type OpenPetsPluginEntry = (api: OpenPetsPluginApi) => void | Promise<void>;
194
842
  /** Field types supported by `configSchema` in the manifest. */
195
- export type OpenPetsConfigFieldType = "text" | "textarea" | "number" | "boolean" | "select" | "time" | "list" | "multiselect";
843
+ export type OpenPetsConfigFieldType = "text" | "textarea" | "number" | "boolean" | "select" | "time" | "date" | "list" | "multiselect" | "sound"
844
+ /** Masked input, encrypted at rest, never logged or echoed (v3). */
845
+ | "secret";
846
+ /** v2 alias: the old flat pet namespace shape. */
847
+ export type OpenPetsPetApi = Pick<OpenPetsPetHandle, "speak" | "react" | "moveBy" | "wander" | "moveToHome">;
196
848
  declare global {
197
849
  /**
198
850
  * Injected by the OpenPets runtime. Call once at the top level of your