@mywallpaper/addon-sdk 2.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.
@@ -0,0 +1,1087 @@
1
+ /**
2
+ * @mywallpaper/addon-sdk - TypeScript Types v2.0
3
+ *
4
+ * Complete type definitions for building MyWallpaper addons.
5
+ * These types describe the runtime API available via window.MyWallpaper
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import type { MyWallpaperAPI, SystemEventType } from '@mywallpaper/addon-sdk'
10
+ *
11
+ * // Type-safe event handling
12
+ * const api = window.MyWallpaper
13
+ * api.onEvent('viewport:resize', ({ width, height }) => {
14
+ * console.log(`Resized to ${width}x${height}`)
15
+ * })
16
+ * ```
17
+ */
18
+ /**
19
+ * System events that addons can subscribe to.
20
+ * Subscribe via `window.MyWallpaper.onEvent(eventType, callback)`
21
+ */
22
+ type SystemEventType = 'viewport:resize' | 'theme:change' | 'visibility:change' | 'layer:focus' | 'layer:blur' | 'app:idle' | 'app:active';
23
+ /**
24
+ * Event data payloads for each system event type.
25
+ * TypeScript will infer the correct payload type based on event name.
26
+ */
27
+ interface SystemEventData {
28
+ 'viewport:resize': {
29
+ width: number;
30
+ height: number;
31
+ aspectRatio: string;
32
+ };
33
+ 'theme:change': {
34
+ theme: 'light' | 'dark' | 'system';
35
+ };
36
+ 'visibility:change': {
37
+ visible: boolean;
38
+ };
39
+ 'layer:focus': {
40
+ layerId: string;
41
+ };
42
+ 'layer:blur': {
43
+ layerId: string;
44
+ };
45
+ 'app:idle': {
46
+ idleSeconds: number;
47
+ };
48
+ 'app:active': Record<string, never>;
49
+ }
50
+ /**
51
+ * Capabilities that an addon can declare.
52
+ * Declared in `manifest.json` and sent via `ready()` call.
53
+ */
54
+ type AddonCapability = 'hot-reload' | 'system-events' | 'storage' | 'audio' | 'network';
55
+ /**
56
+ * OAuth providers supported for addon API access.
57
+ * Use with `oauth:{provider}` permission format.
58
+ */
59
+ type OAuthProvider = 'github' | 'google' | 'discord' | 'spotify' | 'twitch';
60
+ /**
61
+ * OAuth permission types for accessing provider APIs.
62
+ * Format: `oauth:{provider}`
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const permission: OAuthPermissionType = 'oauth:github'
67
+ * await api.requestPermission(permission, 'Display your GitHub profile')
68
+ * ```
69
+ */
70
+ type OAuthPermissionType = 'oauth:github' | 'oauth:google' | 'oauth:discord' | 'oauth:spotify' | 'oauth:twitch';
71
+ /**
72
+ * Permission types that addons can request.
73
+ * Requested via `window.MyWallpaper.requestPermission(type, reason)`
74
+ */
75
+ type PermissionType = 'storage' | 'cpu-high' | 'network' | 'audio' | 'notifications' | OAuthPermissionType;
76
+ /**
77
+ * Constraints that may be applied to granted permissions.
78
+ * Returned when permission is granted with limitations.
79
+ */
80
+ interface PermissionConstraints {
81
+ storage?: {
82
+ quotaKB: number;
83
+ };
84
+ network?: {
85
+ allowedDomains: string[];
86
+ };
87
+ cpu?: {
88
+ maxPercent: number;
89
+ };
90
+ }
91
+ /**
92
+ * Storage API for persistent key-value data.
93
+ * Access via `window.MyWallpaper.storage`
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const { storage } = window.MyWallpaper
98
+ *
99
+ * // Save user preferences
100
+ * await storage.set('preferences', { darkMode: true, fontSize: 14 })
101
+ *
102
+ * // Retrieve data
103
+ * const prefs = await storage.get<{ darkMode: boolean; fontSize: number }>('preferences')
104
+ *
105
+ * // Check storage usage
106
+ * const usedBytes = await storage.size()
107
+ * console.log(`Using ${usedBytes} bytes of storage`)
108
+ * ```
109
+ */
110
+ interface StorageAPI {
111
+ /**
112
+ * Get a value by key.
113
+ * Returns null if key doesn't exist.
114
+ */
115
+ get<T = unknown>(key: string): Promise<T | null>;
116
+ /**
117
+ * Set a value for a key.
118
+ * Throws if quota exceeded (default 1MB per addon).
119
+ */
120
+ set<T = unknown>(key: string, value: T): Promise<void>;
121
+ /**
122
+ * Delete a key and its value.
123
+ */
124
+ delete(key: string): Promise<void>;
125
+ /**
126
+ * Clear all stored data for this addon.
127
+ */
128
+ clear(): Promise<void>;
129
+ /**
130
+ * Get all storage keys.
131
+ */
132
+ keys(): Promise<string[]>;
133
+ /**
134
+ * Get total storage usage in bytes.
135
+ */
136
+ size(): Promise<number>;
137
+ }
138
+ /**
139
+ * Response from network.fetch() requests.
140
+ * Similar to native Response but with pre-parsed data.
141
+ */
142
+ interface NetworkResponse {
143
+ /** Whether the request was successful (status 200-299) */
144
+ ok: boolean;
145
+ /** HTTP status code */
146
+ status: number;
147
+ /** HTTP status text */
148
+ statusText: string;
149
+ /** Response headers */
150
+ headers: Record<string, string>;
151
+ /** Response data (auto-parsed JSON, text, or base64 for binary) */
152
+ data: unknown;
153
+ }
154
+ /**
155
+ * Network API for making HTTP requests through the secure host proxy.
156
+ * Access via `window.MyWallpaper.network`
157
+ *
158
+ * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
159
+ * All network requests MUST go through this API.
160
+ *
161
+ * **IMPORTANT:** Domains must be declared in your manifest.json:
162
+ * ```json
163
+ * {
164
+ * "permissions": {
165
+ * "network": { "domains": ["api.example.com"] }
166
+ * }
167
+ * }
168
+ * ```
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const { network } = window.MyWallpaper
173
+ *
174
+ * // Simple GET request
175
+ * const response = await network.fetch('https://api.weather.com/current')
176
+ * if (response.ok) {
177
+ * console.log(response.data)
178
+ * }
179
+ *
180
+ * // POST request with JSON body
181
+ * const result = await network.fetch('https://api.example.com/data', {
182
+ * method: 'POST',
183
+ * headers: { 'Content-Type': 'application/json' },
184
+ * body: JSON.stringify({ key: 'value' })
185
+ * })
186
+ * ```
187
+ */
188
+ interface NetworkAPI {
189
+ /**
190
+ * Make an HTTP request through the secure host proxy.
191
+ * Domain must be whitelisted in manifest.json permissions.
192
+ *
193
+ * @param url - Full URL to fetch (must be in allowed domains)
194
+ * @param options - Optional request options
195
+ * @returns Promise resolving to NetworkResponse
196
+ * @throws Error if domain not allowed or network permission not granted
197
+ */
198
+ fetch(url: string, options?: {
199
+ /** HTTP method (default: GET) */
200
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
201
+ /** Request headers */
202
+ headers?: Record<string, string>;
203
+ /** Request body (for POST/PUT/PATCH) */
204
+ body?: string;
205
+ }): Promise<NetworkResponse>;
206
+ }
207
+ /**
208
+ * Response from OAuth API proxy requests.
209
+ */
210
+ interface OAuthResponse {
211
+ /** Whether the request was successful (status 200-299) */
212
+ ok: boolean;
213
+ /** HTTP status code from the provider API */
214
+ status: number;
215
+ /** Response headers from the provider API */
216
+ headers: Record<string, string>;
217
+ /** Response data (auto-parsed JSON) */
218
+ data: unknown;
219
+ }
220
+ /**
221
+ * Error when OAuth scopes are insufficient.
222
+ * Returned when the addon needs scopes the current token doesn't have.
223
+ */
224
+ interface OAuthScopesError {
225
+ /** Error code: 'insufficient_scopes' */
226
+ error: 'insufficient_scopes';
227
+ /** Human-readable error message */
228
+ message: string;
229
+ /** The OAuth provider that needs re-authorization */
230
+ provider: OAuthProvider;
231
+ /** Scopes that are missing from the current token */
232
+ missingScopes: string[];
233
+ /** All scopes needed for this request */
234
+ requiredScopes: string[];
235
+ }
236
+ /**
237
+ * OAuth API for making authenticated requests to provider APIs.
238
+ * Access via `window.MyWallpaper.oauth`
239
+ *
240
+ * **How it works:**
241
+ * 1. User connects their OAuth provider in MyWallpaper settings
242
+ * 2. Your addon requests permission (e.g., `oauth:github`)
243
+ * 3. API calls are proxied through the host - your addon never sees the token
244
+ * 4. The host handles token refresh automatically
245
+ *
246
+ * **SECURITY:** Your addon NEVER sees OAuth tokens. All requests are proxied
247
+ * through the backend, which adds authentication headers server-side.
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const api = window.MyWallpaper
252
+ *
253
+ * // Request GitHub permission
254
+ * const granted = await api.requestPermission(
255
+ * 'oauth:github',
256
+ * 'Display your GitHub profile and repositories'
257
+ * )
258
+ *
259
+ * if (granted) {
260
+ * // Make API calls through the proxy
261
+ * const response = await api.oauth.request('github', '/user')
262
+ * if (response.ok) {
263
+ * console.log('Username:', response.data.login)
264
+ * }
265
+ * }
266
+ * ```
267
+ */
268
+ interface OAuthAPI {
269
+ /**
270
+ * Make an authenticated request to an OAuth provider's API.
271
+ *
272
+ * @param provider - The OAuth provider (github, google, etc.)
273
+ * @param endpoint - The API endpoint path (e.g., '/user', '/user/repos')
274
+ * @param options - Optional request options
275
+ * @returns Promise resolving to OAuthResponse or OAuthScopesError
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * // GET request
280
+ * const profile = await api.oauth.request('github', '/user')
281
+ *
282
+ * // POST request
283
+ * const repo = await api.oauth.request('github', '/user/repos', {
284
+ * method: 'POST',
285
+ * body: { name: 'new-repo', private: true }
286
+ * })
287
+ * ```
288
+ */
289
+ request(provider: OAuthProvider, endpoint: string, options?: {
290
+ /** HTTP method (default: GET) */
291
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
292
+ /** Request body (for POST/PUT/PATCH) - will be JSON serialized */
293
+ body?: unknown;
294
+ /** Additional headers (Authorization is added automatically) */
295
+ headers?: Record<string, string>;
296
+ /** Required scopes for this request - triggers re-auth if missing */
297
+ requiredScopes?: string[];
298
+ }): Promise<OAuthResponse | OAuthScopesError>;
299
+ /**
300
+ * Check if the user has connected a specific OAuth provider.
301
+ *
302
+ * @param provider - The OAuth provider to check
303
+ * @returns Promise resolving to true if connected
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * if (await api.oauth.isConnected('github')) {
308
+ * console.log('GitHub is connected')
309
+ * } else {
310
+ * console.log('Please connect your GitHub account')
311
+ * }
312
+ * ```
313
+ */
314
+ isConnected(provider: OAuthProvider): Promise<boolean>;
315
+ /**
316
+ * Get the scopes granted for a connected OAuth provider.
317
+ *
318
+ * @param provider - The OAuth provider
319
+ * @returns Promise resolving to array of granted scopes
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * const scopes = await api.oauth.getScopes('github')
324
+ * if (scopes.includes('repo')) {
325
+ * // Can access private repos
326
+ * }
327
+ * ```
328
+ */
329
+ getScopes(provider: OAuthProvider): Promise<string[]>;
330
+ /**
331
+ * Request re-authorization with additional scopes.
332
+ * Opens the OAuth flow with the new scopes, preserving existing ones.
333
+ *
334
+ * @param provider - The OAuth provider
335
+ * @param scopes - Additional scopes to request
336
+ * @param reason - User-friendly explanation of why scopes are needed
337
+ * @returns Promise resolving to true if re-auth was successful
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * // Request repo scope for private repository access
342
+ * const granted = await api.oauth.requestScopes(
343
+ * 'github',
344
+ * ['repo'],
345
+ * 'Access your private repositories to display commit stats'
346
+ * )
347
+ *
348
+ * if (granted) {
349
+ * // Now we can access private repos
350
+ * const repos = await api.oauth.request('github', '/user/repos')
351
+ * }
352
+ * ```
353
+ */
354
+ requestScopes(provider: OAuthProvider, scopes: string[], reason: string): Promise<boolean>;
355
+ }
356
+ /**
357
+ * Callback for settings changes (hot-reload).
358
+ * @param settings - Complete settings object with new values
359
+ * @param changedKeys - Array of keys that changed (for optimization)
360
+ */
361
+ interface SettingsCallback {
362
+ (settings: Record<string, unknown>, changedKeys: string[]): void;
363
+ }
364
+ /**
365
+ * Generic callback for system events.
366
+ * @param data - Event-specific payload
367
+ */
368
+ interface EventCallback<T = unknown> {
369
+ (data: T): void;
370
+ }
371
+ /**
372
+ * Options passed to `ready()` call.
373
+ */
374
+ interface ReadyOptions {
375
+ /** Capabilities this addon supports */
376
+ capabilities?: AddonCapability[];
377
+ /** System events this addon wants to receive */
378
+ subscribedEvents?: SystemEventType[];
379
+ }
380
+ /**
381
+ * The main MyWallpaper API available on `window.MyWallpaper`.
382
+ *
383
+ * This API is automatically injected when your addon loads.
384
+ * No imports needed - just use `window.MyWallpaper` in your code.
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * // Type-safe access
389
+ * declare const window: Window & { MyWallpaper: MyWallpaperAPI }
390
+ *
391
+ * const { config, storage } = window.MyWallpaper
392
+ *
393
+ * // React to settings changes
394
+ * window.MyWallpaper.onSettingsChange((settings, changedKeys) => {
395
+ * if (changedKeys.includes('primaryColor')) {
396
+ * updateTheme(settings.primaryColor as string)
397
+ * }
398
+ * })
399
+ *
400
+ * // Signal addon is ready
401
+ * window.MyWallpaper.ready({
402
+ * capabilities: ['hot-reload', 'storage'],
403
+ * subscribedEvents: ['viewport:resize', 'theme:change']
404
+ * })
405
+ * ```
406
+ */
407
+ interface MyWallpaperAPI {
408
+ /**
409
+ * Current configuration values from user settings.
410
+ * Updated automatically when settings change.
411
+ */
412
+ readonly config: Record<string, unknown>;
413
+ /**
414
+ * Unique identifier for this addon layer instance.
415
+ * Useful for multi-instance scenarios.
416
+ */
417
+ readonly layerId: string;
418
+ /**
419
+ * SDK version. Always '2.0' for current API.
420
+ */
421
+ readonly version: '2.0';
422
+ /**
423
+ * Register callback for settings changes (hot-reload).
424
+ * Called when user modifies addon settings in the UI.
425
+ *
426
+ * @param callback - Function called with new settings and changed keys
427
+ */
428
+ onSettingsChange(callback: SettingsCallback): void;
429
+ /**
430
+ * Subscribe to a system event.
431
+ * @param event - Event type to subscribe to
432
+ * @param callback - Function called when event fires
433
+ */
434
+ onEvent<E extends SystemEventType>(event: E, callback: EventCallback<SystemEventData[E]>): void;
435
+ /**
436
+ * Unsubscribe from a system event.
437
+ * @param event - Event type to unsubscribe from
438
+ * @param callback - Same callback function passed to onEvent
439
+ */
440
+ offEvent<E extends SystemEventType>(event: E, callback: EventCallback<SystemEventData[E]>): void;
441
+ /**
442
+ * Called when addon is mounted (first load).
443
+ */
444
+ onMount(callback: () => void): void;
445
+ /**
446
+ * Called when addon is unmounted (removed from page).
447
+ * Use for cleanup (event listeners, timers, etc.).
448
+ */
449
+ onUnmount(callback: () => void): void;
450
+ /**
451
+ * Called when addon is paused (hidden, tab inactive).
452
+ * Use to pause animations and reduce resource usage.
453
+ */
454
+ onPause(callback: () => void): void;
455
+ /**
456
+ * Called when addon is resumed (visible again).
457
+ * Use to restart animations and refresh data.
458
+ */
459
+ onResume(callback: () => void): void;
460
+ /**
461
+ * Signal that the addon is ready and initialized.
462
+ * **Must be called** after your addon loads and initializes.
463
+ *
464
+ * @param options - Capabilities and event subscriptions
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * // After DOM is ready and addon is initialized
469
+ * window.MyWallpaper.ready({
470
+ * capabilities: ['hot-reload'],
471
+ * subscribedEvents: ['viewport:resize']
472
+ * })
473
+ * ```
474
+ */
475
+ ready(options?: ReadyOptions): void;
476
+ /**
477
+ * Signal that visual rendering is complete.
478
+ * Call after images are loaded, canvas drawn, animations ready.
479
+ * Used by screenshot service to capture addon at right moment.
480
+ *
481
+ * @example
482
+ * ```typescript
483
+ * async function init() {
484
+ * await loadAllImages()
485
+ * drawCanvas()
486
+ * window.MyWallpaper.renderComplete()
487
+ * }
488
+ * ```
489
+ */
490
+ renderComplete(): void;
491
+ /**
492
+ * Request a permission from the user.
493
+ * Shows a permission modal explaining why the permission is needed.
494
+ *
495
+ * @param permission - Permission type to request
496
+ * @param reason - User-friendly explanation of why permission is needed
497
+ * @returns Promise resolving to true if granted, false if denied
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * const granted = await window.MyWallpaper.requestPermission(
502
+ * 'storage',
503
+ * 'Save your preferences across sessions'
504
+ * )
505
+ * if (granted) {
506
+ * await window.MyWallpaper.storage.set('initialized', true)
507
+ * }
508
+ * ```
509
+ */
510
+ requestPermission(permission: PermissionType, reason: string): Promise<boolean>;
511
+ /**
512
+ * Persistent key-value storage API.
513
+ * Requires 'storage' permission to be granted first.
514
+ * Data syncs across devices for logged-in users.
515
+ */
516
+ storage: StorageAPI;
517
+ /**
518
+ * Network API for making HTTP requests through the secure host proxy.
519
+ * Requires 'network' permission and domains declared in manifest.
520
+ *
521
+ * **SECURITY:** Direct `fetch()` and `XMLHttpRequest` are blocked.
522
+ * This is the ONLY way to make network requests from addons.
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * // First, request permission
527
+ * const granted = await MyWallpaper.requestPermission('network', 'Fetch weather data')
528
+ * if (granted) {
529
+ * const response = await MyWallpaper.network.fetch('https://api.weather.com/current')
530
+ * if (response.ok) {
531
+ * console.log(response.data)
532
+ * }
533
+ * }
534
+ * ```
535
+ */
536
+ network: NetworkAPI;
537
+ /**
538
+ * OAuth API for making authenticated requests to provider APIs.
539
+ * Requires corresponding oauth permission (e.g., 'oauth:github').
540
+ *
541
+ * **SECURITY:** Your addon NEVER sees OAuth tokens. All requests are
542
+ * proxied through the backend which adds authentication server-side.
543
+ *
544
+ * **How it works:**
545
+ * 1. User connects their OAuth provider in MyWallpaper settings
546
+ * 2. Your addon requests permission (e.g., `oauth:github`)
547
+ * 3. API calls are proxied through the host
548
+ * 4. The host handles token refresh automatically
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * // First, request GitHub permission
553
+ * const granted = await MyWallpaper.requestPermission(
554
+ * 'oauth:github',
555
+ * 'Display your GitHub profile'
556
+ * )
557
+ *
558
+ * if (granted) {
559
+ * // Make authenticated API calls
560
+ * const response = await MyWallpaper.oauth.request('github', '/user')
561
+ * if (response.ok) {
562
+ * console.log('GitHub username:', response.data.login)
563
+ * }
564
+ * }
565
+ * ```
566
+ */
567
+ oauth: OAuthAPI;
568
+ }
569
+ /**
570
+ * Augments the global Window interface with MyWallpaper API.
571
+ * Allows `window.MyWallpaper` to be used without type assertions.
572
+ */
573
+ declare global {
574
+ interface Window {
575
+ /** Layer ID injected before addon loads */
576
+ __MYWALLPAPER_LAYER_ID__?: string;
577
+ /** Initial config injected before addon loads */
578
+ __MYWALLPAPER_INITIAL_CONFIG__?: Record<string, unknown>;
579
+ /** The main MyWallpaper API */
580
+ MyWallpaper: MyWallpaperAPI;
581
+ }
582
+ }
583
+ /**
584
+ * Lifecycle event names.
585
+ */
586
+ type LifecycleEvent = 'mount' | 'unmount' | 'pause' | 'resume';
587
+ /**
588
+ * Generic addon values (settings configuration).
589
+ */
590
+ type AddonValues = Record<string, unknown>;
591
+
592
+ /**
593
+ * @mywallpaper/addon-sdk - Manifest Schema & Validation
594
+ *
595
+ * Defines the addon manifest format (manifest.json) and provides
596
+ * Zod-based validation for type safety at runtime.
597
+ *
598
+ * @example
599
+ * ```typescript
600
+ * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'
601
+ *
602
+ * const manifest: AddonManifest = {
603
+ * name: 'My Addon',
604
+ * version: '1.0.0',
605
+ * description: 'A cool addon',
606
+ * settings: {
607
+ * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }
608
+ * }
609
+ * }
610
+ *
611
+ * // Validate at runtime
612
+ * const result = validateManifest(manifest)
613
+ * if (!result.success) {
614
+ * console.error('Invalid manifest:', result.errors)
615
+ * }
616
+ * ```
617
+ */
618
+
619
+ /**
620
+ * Available setting types for addon configuration.
621
+ */
622
+ type SettingType = 'string' | 'number' | 'boolean' | 'color' | 'select' | 'range' | 'image' | 'section' | 'textarea' | 'radio' | 'gradient' | 'vector2' | 'button' | 'multi-select';
623
+ /**
624
+ * Definition for a single addon setting.
625
+ *
626
+ * @example
627
+ * ```typescript
628
+ * const colorSetting: SettingDefinition = {
629
+ * type: 'color',
630
+ * label: 'Background Color',
631
+ * description: 'Choose a background color',
632
+ * default: '#000000'
633
+ * }
634
+ *
635
+ * const speedSetting: SettingDefinition = {
636
+ * type: 'range',
637
+ * label: 'Animation Speed',
638
+ * min: 0.1,
639
+ * max: 10,
640
+ * step: 0.1,
641
+ * default: 1
642
+ * }
643
+ * ```
644
+ */
645
+ interface SettingDefinition {
646
+ /** Setting type determines the UI control */
647
+ type: SettingType;
648
+ /** Human-readable label */
649
+ label?: string;
650
+ /** Help text shown below the control */
651
+ description?: string;
652
+ /** Default value (type depends on setting type) */
653
+ default?: unknown;
654
+ /** Options for select, radio, multi-select types */
655
+ options?: Array<{
656
+ value: string;
657
+ label: string;
658
+ }>;
659
+ /** Minimum value for range type */
660
+ min?: number;
661
+ /** Maximum value for range type */
662
+ max?: number;
663
+ /** Step increment for range type */
664
+ step?: number;
665
+ /** Label text on button */
666
+ buttonLabel?: string;
667
+ /** Axis labels for vector types, e.g., ['X', 'Y'] */
668
+ axisLabels?: [string, string] | [string, string, string];
669
+ /** Maximum number of selections */
670
+ maxItems?: number;
671
+ /** Only show if another setting has a specific value */
672
+ showIf?: {
673
+ setting: string;
674
+ equals: unknown;
675
+ };
676
+ }
677
+ /**
678
+ * Default layout configuration for addon positioning.
679
+ */
680
+ interface AddonDefaultLayout {
681
+ /** X position as percentage of viewport width (0-100) */
682
+ xPercent?: number;
683
+ /** Y position as percentage of viewport height (0-100) */
684
+ yPercent?: number;
685
+ /** Width as percentage of viewport width (0-100) */
686
+ widthPercent?: number;
687
+ /** Height as percentage of viewport height (0-100) */
688
+ heightPercent?: number;
689
+ /** Rotation in degrees */
690
+ rotation?: number;
691
+ }
692
+ /**
693
+ * The complete addon manifest schema.
694
+ * This defines your addon's identity, settings, and capabilities.
695
+ *
696
+ * @example
697
+ * ```json
698
+ * {
699
+ * "name": "Weather Widget",
700
+ * "version": "1.2.0",
701
+ * "description": "Displays current weather conditions",
702
+ * "author": "Jane Developer",
703
+ * "categories": ["utilities", "weather"],
704
+ * "capabilities": {
705
+ * "hotReload": true,
706
+ * "systemEvents": ["viewport:resize", "theme:change"]
707
+ * },
708
+ * "permissions": {
709
+ * "storage": { "quota": 512 },
710
+ * "network": { "domains": ["api.openweathermap.org"] }
711
+ * },
712
+ * "settings": {
713
+ * "apiKey": {
714
+ * "type": "string",
715
+ * "label": "API Key",
716
+ * "description": "Get your key at openweathermap.org"
717
+ * },
718
+ * "unit": {
719
+ * "type": "select",
720
+ * "label": "Temperature Unit",
721
+ * "default": "celsius",
722
+ * "options": [
723
+ * { "value": "celsius", "label": "Celsius" },
724
+ * { "value": "fahrenheit", "label": "Fahrenheit" }
725
+ * ]
726
+ * }
727
+ * }
728
+ * }
729
+ * ```
730
+ */
731
+ interface AddonManifest {
732
+ /** Addon name (displayed in UI) */
733
+ name: string;
734
+ /** Semantic version (e.g., "1.0.0") */
735
+ version: string;
736
+ /** Short description of the addon */
737
+ description?: string;
738
+ /** Author name or organization */
739
+ author?: string;
740
+ /** Addon type/category for filtering */
741
+ type?: string;
742
+ /** Categories for discoverability */
743
+ categories?: string[];
744
+ /** License (e.g., "MIT", "Apache-2.0") */
745
+ license?: string;
746
+ /** Homepage or documentation URL */
747
+ homepage?: string;
748
+ /** Repository URL */
749
+ repository?: string;
750
+ /**
751
+ * User-configurable settings.
752
+ * Key is the setting ID, value is the definition.
753
+ */
754
+ settings?: Record<string, SettingDefinition>;
755
+ /**
756
+ * Addon capabilities and event subscriptions.
757
+ */
758
+ capabilities?: {
759
+ /** Supports settings changes without full reload */
760
+ hotReload?: boolean;
761
+ /** System events the addon wants to receive */
762
+ systemEvents?: SystemEventType[];
763
+ };
764
+ /**
765
+ * Permissions the addon will request.
766
+ * Declaring them here improves UX by showing upfront.
767
+ */
768
+ permissions?: {
769
+ /** Storage permission with quota in KB */
770
+ storage?: {
771
+ quota: number;
772
+ };
773
+ /** CPU usage limit */
774
+ cpu?: {
775
+ limit: 'low' | 'medium' | 'high';
776
+ };
777
+ /** Network access with allowed domains */
778
+ network?: {
779
+ domains: string[];
780
+ };
781
+ /** Audio playback permission */
782
+ audio?: boolean;
783
+ /** Notification permission */
784
+ notifications?: boolean;
785
+ };
786
+ /**
787
+ * Default layout for automatic positioning.
788
+ * Used when addon is first added to a wallpaper.
789
+ */
790
+ defaultLayout?: AddonDefaultLayout;
791
+ /**
792
+ * OAuth providers and scopes for authenticated addons.
793
+ *
794
+ * Declare the OAuth providers your addon needs and the scopes it requires.
795
+ * Scopes are requested incrementally - users can connect with basic scopes
796
+ * first, and your addon can request additional scopes when needed.
797
+ *
798
+ * @example
799
+ * ```json
800
+ * {
801
+ * "permissions": {
802
+ * "oauth": {
803
+ * "required": [
804
+ * { "provider": "github", "scopes": ["read:user"] }
805
+ * ],
806
+ * "optional": [
807
+ * { "provider": "github", "scopes": ["repo"] }
808
+ * ]
809
+ * }
810
+ * }
811
+ * }
812
+ * ```
813
+ */
814
+ oauth?: {
815
+ /** OAuth providers required for the addon to function */
816
+ required?: Array<{
817
+ provider: OAuthProvider;
818
+ scopes: string[];
819
+ /** Reason shown to user when requesting this permission */
820
+ reason?: string;
821
+ }>;
822
+ /** OAuth providers that enhance functionality but aren't required */
823
+ optional?: Array<{
824
+ provider: OAuthProvider;
825
+ scopes: string[];
826
+ /** Reason shown to user when requesting this permission */
827
+ reason?: string;
828
+ }>;
829
+ };
830
+ }
831
+ /**
832
+ * Validation result type.
833
+ */
834
+ interface ValidationResult {
835
+ success: boolean;
836
+ errors?: string[];
837
+ manifest?: AddonManifest;
838
+ }
839
+ /**
840
+ * Validates an addon manifest object.
841
+ *
842
+ * @param input - The manifest object to validate
843
+ * @returns ValidationResult with success status and any errors
844
+ *
845
+ * @example
846
+ * ```typescript
847
+ * const result = validateManifest(myManifest)
848
+ * if (!result.success) {
849
+ * result.errors?.forEach(err => console.error(err))
850
+ * }
851
+ * ```
852
+ */
853
+ declare function validateManifest(input: unknown): ValidationResult;
854
+ /**
855
+ * Type guard to check if an object is a valid AddonManifest.
856
+ *
857
+ * @param input - Object to check
858
+ * @returns True if input is a valid AddonManifest
859
+ */
860
+ declare function isValidManifest(input: unknown): input is AddonManifest;
861
+ /**
862
+ * Check if manifest declares hot-reload capability.
863
+ */
864
+ declare function supportsHotReload(manifest: AddonManifest): boolean;
865
+ /**
866
+ * Get system events the addon subscribes to.
867
+ */
868
+ declare function getSubscribedEvents(manifest: AddonManifest): SystemEventType[];
869
+ /**
870
+ * Get required permissions from manifest.
871
+ */
872
+ declare function getRequiredPermissions(manifest: AddonManifest): PermissionType[];
873
+
874
+ /**
875
+ * @mywallpaper/addon-sdk - Utility Functions
876
+ *
877
+ * Helper utilities for addon development.
878
+ */
879
+
880
+ /**
881
+ * Creates a default configuration object from manifest settings.
882
+ *
883
+ * @param manifest - Addon manifest with settings definitions
884
+ * @returns Object with all setting keys and their default values
885
+ *
886
+ * @example
887
+ * ```typescript
888
+ * const manifest = {
889
+ * name: 'My Addon',
890
+ * version: '1.0.0',
891
+ * settings: {
892
+ * color: { type: 'color', default: '#ff0000' },
893
+ * speed: { type: 'range', default: 1, min: 0, max: 10 }
894
+ * }
895
+ * }
896
+ *
897
+ * const defaults = createDefaultConfig(manifest)
898
+ * // { color: '#ff0000', speed: 1 }
899
+ * ```
900
+ */
901
+ declare function createDefaultConfig(manifest: AddonManifest): Record<string, unknown>;
902
+ /**
903
+ * Merges user config with defaults, ensuring all keys exist.
904
+ *
905
+ * @param defaults - Default configuration
906
+ * @param userConfig - User-provided configuration (may be partial)
907
+ * @returns Complete configuration with user values taking precedence
908
+ *
909
+ * @example
910
+ * ```typescript
911
+ * const defaults = { color: '#ff0000', speed: 1 }
912
+ * const user = { color: '#00ff00' }
913
+ *
914
+ * const config = mergeConfigs(defaults, user)
915
+ * // { color: '#00ff00', speed: 1 }
916
+ * ```
917
+ */
918
+ declare function mergeConfigs(defaults: Record<string, unknown>, userConfig: Record<string, unknown>): Record<string, unknown>;
919
+ /**
920
+ * Semantic version comparison result.
921
+ */
922
+ type VersionComparison = -1 | 0 | 1;
923
+ /**
924
+ * Compare two semantic versions.
925
+ *
926
+ * @param a - First version string
927
+ * @param b - Second version string
928
+ * @returns -1 if a < b, 0 if a === b, 1 if a > b
929
+ *
930
+ * @example
931
+ * ```typescript
932
+ * compareVersions('1.0.0', '1.0.1') // -1
933
+ * compareVersions('2.0.0', '1.9.9') // 1
934
+ * compareVersions('1.0.0', '1.0.0') // 0
935
+ * ```
936
+ */
937
+ declare function compareVersions(a: string, b: string): VersionComparison;
938
+ /**
939
+ * Check if version satisfies a minimum requirement.
940
+ *
941
+ * @param version - Version to check
942
+ * @param minimum - Minimum required version
943
+ * @returns True if version >= minimum
944
+ */
945
+ declare function satisfiesMinVersion(version: string, minimum: string): boolean;
946
+ /**
947
+ * Calculate the byte size of a value when JSON serialized.
948
+ * Useful for quota tracking.
949
+ *
950
+ * @param value - Value to measure
951
+ * @returns Size in bytes
952
+ */
953
+ declare function getStorageSize(value: unknown): number;
954
+ /**
955
+ * Format bytes as human-readable string.
956
+ *
957
+ * @param bytes - Number of bytes
958
+ * @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
959
+ */
960
+ declare function formatBytes(bytes: number): string;
961
+ /**
962
+ * Check if a value is a plain object.
963
+ */
964
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
965
+ /**
966
+ * Check if a value is a valid color hex string.
967
+ */
968
+ declare function isValidColor(value: unknown): value is string;
969
+ /**
970
+ * Debounce a function - only call after delay with no new calls.
971
+ *
972
+ * @param fn - Function to debounce
973
+ * @param delayMs - Delay in milliseconds
974
+ * @returns Debounced function
975
+ *
976
+ * @example
977
+ * ```typescript
978
+ * const save = debounce((data) => {
979
+ * localStorage.setItem('data', JSON.stringify(data))
980
+ * }, 1000)
981
+ *
982
+ * // Rapid calls only trigger one save after 1s
983
+ * save({ a: 1 })
984
+ * save({ a: 2 })
985
+ * save({ a: 3 }) // Only this one runs (after 1s)
986
+ * ```
987
+ */
988
+ declare function debounce<T extends (...args: unknown[]) => void>(fn: T, delayMs: number): (...args: Parameters<T>) => void;
989
+ /**
990
+ * Throttle a function - call at most once per interval.
991
+ *
992
+ * @param fn - Function to throttle
993
+ * @param intervalMs - Minimum interval between calls
994
+ * @returns Throttled function
995
+ */
996
+ declare function throttle<T extends (...args: unknown[]) => void>(fn: T, intervalMs: number): (...args: Parameters<T>) => void;
997
+ /**
998
+ * Create a one-time event listener that auto-removes after first call.
999
+ *
1000
+ * @param element - DOM element or window
1001
+ * @param event - Event name
1002
+ * @param handler - Event handler
1003
+ */
1004
+ declare function once(element: EventTarget, event: string, handler: EventListener): void;
1005
+
1006
+ /**
1007
+ * @mywallpaper/addon-sdk
1008
+ *
1009
+ * Official SDK for building MyWallpaper addons.
1010
+ *
1011
+ * @packageDocumentation
1012
+ *
1013
+ * ## Installation
1014
+ *
1015
+ * ```bash
1016
+ * npm install @mywallpaper/addon-sdk
1017
+ * ```
1018
+ *
1019
+ * ## Quick Start
1020
+ *
1021
+ * ```typescript
1022
+ * // Your addon's main file
1023
+ * import type { MyWallpaperAPI } from '@mywallpaper/addon-sdk'
1024
+ *
1025
+ * // The API is automatically available on window.MyWallpaper
1026
+ * const api = window.MyWallpaper
1027
+ *
1028
+ * // Access configuration
1029
+ * const { backgroundColor, animationSpeed } = api.config
1030
+ *
1031
+ * // React to setting changes (hot-reload)
1032
+ * api.onSettingsChange((settings, changedKeys) => {
1033
+ * if (changedKeys.includes('backgroundColor')) {
1034
+ * document.body.style.background = settings.backgroundColor as string
1035
+ * }
1036
+ * })
1037
+ *
1038
+ * // Subscribe to system events
1039
+ * api.onEvent('viewport:resize', ({ width, height }) => {
1040
+ * canvas.width = width
1041
+ * canvas.height = height
1042
+ * })
1043
+ *
1044
+ * // Signal addon is ready
1045
+ * api.ready({
1046
+ * capabilities: ['hot-reload'],
1047
+ * subscribedEvents: ['viewport:resize']
1048
+ * })
1049
+ *
1050
+ * // After visual rendering is complete
1051
+ * await loadImages()
1052
+ * render()
1053
+ * api.renderComplete()
1054
+ * ```
1055
+ *
1056
+ * ## Manifest Validation
1057
+ *
1058
+ * ```typescript
1059
+ * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'
1060
+ *
1061
+ * const manifest: AddonManifest = {
1062
+ * name: 'My Addon',
1063
+ * version: '1.0.0',
1064
+ * settings: {
1065
+ * color: { type: 'color', default: '#ffffff' }
1066
+ * }
1067
+ * }
1068
+ *
1069
+ * const result = validateManifest(manifest)
1070
+ * if (!result.success) {
1071
+ * console.error(result.errors)
1072
+ * }
1073
+ * ```
1074
+ *
1075
+ * @module
1076
+ */
1077
+
1078
+ /**
1079
+ * Current SDK version.
1080
+ */
1081
+ declare const SDK_VERSION = "2.0.0";
1082
+ /**
1083
+ * Minimum compatible host version.
1084
+ */
1085
+ declare const MIN_HOST_VERSION = "2.0.0";
1086
+
1087
+ export { type AddonCapability, type AddonDefaultLayout, type AddonManifest, type AddonValues, type EventCallback, type LifecycleEvent, MIN_HOST_VERSION, type MyWallpaperAPI, type NetworkAPI, type NetworkResponse, type OAuthAPI, type OAuthPermissionType, type OAuthProvider, type OAuthResponse, type OAuthScopesError, type PermissionConstraints, type PermissionType, type ReadyOptions, SDK_VERSION, type SettingDefinition, type SettingType, type SettingsCallback, type StorageAPI, type SystemEventData, type SystemEventType, type ValidationResult, type VersionComparison, compareVersions, createDefaultConfig, debounce, formatBytes, getRequiredPermissions, getStorageSize, getSubscribedEvents, isPlainObject, isValidColor, isValidManifest, mergeConfigs, once, satisfiesMinVersion, supportsHotReload, throttle, validateManifest };