@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,851 @@
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
+ * Storage API for persistent key-value data.
78
+ * Access via `window.MyWallpaper.storage`
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const { storage } = window.MyWallpaper
83
+ *
84
+ * // Save user preferences
85
+ * await storage.set('preferences', { darkMode: true, fontSize: 14 })
86
+ *
87
+ * // Retrieve data
88
+ * const prefs = await storage.get<{ darkMode: boolean; fontSize: number }>('preferences')
89
+ *
90
+ * // Check storage usage
91
+ * const usedBytes = await storage.size()
92
+ * console.log(`Using ${usedBytes} bytes of storage`)
93
+ * ```
94
+ */
95
+ interface StorageAPI {
96
+ /**
97
+ * Get a value by key.
98
+ * Returns null if key doesn't exist.
99
+ */
100
+ get<T = unknown>(key: string): Promise<T | null>;
101
+ /**
102
+ * Set a value for a key.
103
+ * Throws if quota exceeded (default 1MB per addon).
104
+ */
105
+ set<T = unknown>(key: string, value: T): Promise<void>;
106
+ /**
107
+ * Delete a key and its value.
108
+ */
109
+ delete(key: string): Promise<void>;
110
+ /**
111
+ * Clear all stored data for this addon.
112
+ */
113
+ clear(): Promise<void>;
114
+ /**
115
+ * Get all storage keys.
116
+ */
117
+ keys(): Promise<string[]>;
118
+ /**
119
+ * Get total storage usage in bytes.
120
+ */
121
+ size(): Promise<number>;
122
+ }
123
+ /**
124
+ * Response from network.fetch() requests.
125
+ * Similar to native Response but with pre-parsed data.
126
+ */
127
+ interface NetworkResponse {
128
+ /** Whether the request was successful (status 200-299) */
129
+ ok: boolean;
130
+ /** HTTP status code */
131
+ status: number;
132
+ /** HTTP status text */
133
+ statusText: string;
134
+ /** Response headers */
135
+ headers: Record<string, string>;
136
+ /** Response data (auto-parsed JSON, text, or base64 for binary) */
137
+ data: unknown;
138
+ }
139
+ /**
140
+ * Network API for making HTTP requests through the secure host proxy.
141
+ * Access via `window.MyWallpaper.network`
142
+ *
143
+ * **IMPORTANT:** Direct `fetch()` and `XMLHttpRequest` are blocked for security.
144
+ * All network requests MUST go through this API.
145
+ *
146
+ * **IMPORTANT:** Domains must be declared in your manifest.json:
147
+ * ```json
148
+ * {
149
+ * "permissions": {
150
+ * "network": { "domains": ["api.example.com"] }
151
+ * }
152
+ * }
153
+ * ```
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const { network } = window.MyWallpaper
158
+ *
159
+ * // Simple GET request
160
+ * const response = await network.fetch('https://api.weather.com/current')
161
+ * if (response.ok) {
162
+ * console.log(response.data)
163
+ * }
164
+ *
165
+ * // POST request with JSON body
166
+ * const result = await network.fetch('https://api.example.com/data', {
167
+ * method: 'POST',
168
+ * headers: { 'Content-Type': 'application/json' },
169
+ * body: JSON.stringify({ key: 'value' })
170
+ * })
171
+ * ```
172
+ */
173
+ interface NetworkAPI {
174
+ /**
175
+ * Make an HTTP request through the secure host proxy.
176
+ * Domain must be whitelisted in manifest.json permissions.
177
+ *
178
+ * @param url - Full URL to fetch (must be in allowed domains)
179
+ * @param options - Optional request options
180
+ * @returns Promise resolving to NetworkResponse
181
+ * @throws Error if domain not allowed or network permission not granted
182
+ */
183
+ fetch(url: string, options?: {
184
+ /** HTTP method (default: GET) */
185
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
186
+ /** Request headers */
187
+ headers?: Record<string, string>;
188
+ /** Request body (for POST/PUT/PATCH) */
189
+ body?: string;
190
+ }): Promise<NetworkResponse>;
191
+ }
192
+ /**
193
+ * Response from OAuth API proxy requests.
194
+ */
195
+ interface OAuthResponse {
196
+ /** Whether the request was successful (status 200-299) */
197
+ ok: boolean;
198
+ /** HTTP status code from the provider API */
199
+ status: number;
200
+ /** Response headers from the provider API */
201
+ headers: Record<string, string>;
202
+ /** Response data (auto-parsed JSON) */
203
+ data: unknown;
204
+ }
205
+ /**
206
+ * Error when OAuth scopes are insufficient.
207
+ * Returned when the addon needs scopes the current token doesn't have.
208
+ */
209
+ interface OAuthScopesError {
210
+ /** Error code: 'insufficient_scopes' */
211
+ error: 'insufficient_scopes';
212
+ /** Human-readable error message */
213
+ message: string;
214
+ /** The OAuth provider that needs re-authorization */
215
+ provider: OAuthProvider;
216
+ /** Scopes that are missing from the current token */
217
+ missingScopes: string[];
218
+ /** All scopes needed for this request */
219
+ requiredScopes: string[];
220
+ }
221
+ /**
222
+ * OAuth API for making authenticated requests to provider APIs.
223
+ * Access via `window.MyWallpaper.oauth`
224
+ *
225
+ * **How it works:**
226
+ * 1. User connects their OAuth provider in MyWallpaper settings
227
+ * 2. Your addon requests permission (e.g., `oauth:github`)
228
+ * 3. API calls are proxied through the host - your addon never sees the token
229
+ * 4. The host handles token refresh automatically
230
+ *
231
+ * **SECURITY:** Your addon NEVER sees OAuth tokens. All requests are proxied
232
+ * through the backend, which adds authentication headers server-side.
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * const api = window.MyWallpaper
237
+ *
238
+ * // Request GitHub permission
239
+ * const granted = await api.requestPermission(
240
+ * 'oauth:github',
241
+ * 'Display your GitHub profile and repositories'
242
+ * )
243
+ *
244
+ * if (granted) {
245
+ * // Make API calls through the proxy
246
+ * const response = await api.oauth.request('github', '/user')
247
+ * if (response.ok) {
248
+ * console.log('Username:', response.data.login)
249
+ * }
250
+ * }
251
+ * ```
252
+ */
253
+ interface OAuthAPI {
254
+ /**
255
+ * Make an authenticated request to an OAuth provider's API.
256
+ *
257
+ * @param provider - The OAuth provider (github, google, etc.)
258
+ * @param endpoint - The API endpoint path (e.g., '/user', '/user/repos')
259
+ * @param options - Optional request options
260
+ * @returns Promise resolving to OAuthResponse or OAuthScopesError
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * // GET request
265
+ * const profile = await api.oauth.request('github', '/user')
266
+ *
267
+ * // POST request
268
+ * const repo = await api.oauth.request('github', '/user/repos', {
269
+ * method: 'POST',
270
+ * body: { name: 'new-repo', private: true }
271
+ * })
272
+ * ```
273
+ */
274
+ request(provider: OAuthProvider, endpoint: string, options?: {
275
+ /** HTTP method (default: GET) */
276
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
277
+ /** Request body (for POST/PUT/PATCH) - will be JSON serialized */
278
+ body?: unknown;
279
+ /** Additional headers (Authorization is added automatically) */
280
+ headers?: Record<string, string>;
281
+ /** Required scopes for this request - triggers re-auth if missing */
282
+ requiredScopes?: string[];
283
+ }): Promise<OAuthResponse | OAuthScopesError>;
284
+ /**
285
+ * Check if the user has connected a specific OAuth provider.
286
+ *
287
+ * @param provider - The OAuth provider to check
288
+ * @returns Promise resolving to true if connected
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * if (await api.oauth.isConnected('github')) {
293
+ * console.log('GitHub is connected')
294
+ * } else {
295
+ * console.log('Please connect your GitHub account')
296
+ * }
297
+ * ```
298
+ */
299
+ isConnected(provider: OAuthProvider): Promise<boolean>;
300
+ /**
301
+ * Get the scopes granted for a connected OAuth provider.
302
+ *
303
+ * @param provider - The OAuth provider
304
+ * @returns Promise resolving to array of granted scopes
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * const scopes = await api.oauth.getScopes('github')
309
+ * if (scopes.includes('repo')) {
310
+ * // Can access private repos
311
+ * }
312
+ * ```
313
+ */
314
+ getScopes(provider: OAuthProvider): Promise<string[]>;
315
+ /**
316
+ * Request re-authorization with additional scopes.
317
+ * Opens the OAuth flow with the new scopes, preserving existing ones.
318
+ *
319
+ * @param provider - The OAuth provider
320
+ * @param scopes - Additional scopes to request
321
+ * @param reason - User-friendly explanation of why scopes are needed
322
+ * @returns Promise resolving to true if re-auth was successful
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * // Request repo scope for private repository access
327
+ * const granted = await api.oauth.requestScopes(
328
+ * 'github',
329
+ * ['repo'],
330
+ * 'Access your private repositories to display commit stats'
331
+ * )
332
+ *
333
+ * if (granted) {
334
+ * // Now we can access private repos
335
+ * const repos = await api.oauth.request('github', '/user/repos')
336
+ * }
337
+ * ```
338
+ */
339
+ requestScopes(provider: OAuthProvider, scopes: string[], reason: string): Promise<boolean>;
340
+ }
341
+ /**
342
+ * Callback for settings changes (hot-reload).
343
+ * @param settings - Complete settings object with new values
344
+ * @param changedKeys - Array of keys that changed (for optimization)
345
+ */
346
+ interface SettingsCallback {
347
+ (settings: Record<string, unknown>, changedKeys: string[]): void;
348
+ }
349
+ /**
350
+ * Generic callback for system events.
351
+ * @param data - Event-specific payload
352
+ */
353
+ interface EventCallback<T = unknown> {
354
+ (data: T): void;
355
+ }
356
+ /**
357
+ * Options passed to `ready()` call.
358
+ */
359
+ interface ReadyOptions {
360
+ /** Capabilities this addon supports */
361
+ capabilities?: AddonCapability[];
362
+ /** System events this addon wants to receive */
363
+ subscribedEvents?: SystemEventType[];
364
+ }
365
+ /**
366
+ * The main MyWallpaper API available on `window.MyWallpaper`.
367
+ *
368
+ * This API is automatically injected when your addon loads.
369
+ * No imports needed - just use `window.MyWallpaper` in your code.
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * // Type-safe access
374
+ * declare const window: Window & { MyWallpaper: MyWallpaperAPI }
375
+ *
376
+ * const { config, storage } = window.MyWallpaper
377
+ *
378
+ * // React to settings changes
379
+ * window.MyWallpaper.onSettingsChange((settings, changedKeys) => {
380
+ * if (changedKeys.includes('primaryColor')) {
381
+ * updateTheme(settings.primaryColor as string)
382
+ * }
383
+ * })
384
+ *
385
+ * // Signal addon is ready
386
+ * window.MyWallpaper.ready({
387
+ * capabilities: ['hot-reload', 'storage'],
388
+ * subscribedEvents: ['viewport:resize', 'theme:change']
389
+ * })
390
+ * ```
391
+ */
392
+ interface MyWallpaperAPI {
393
+ /**
394
+ * Current configuration values from user settings.
395
+ * Updated automatically when settings change.
396
+ */
397
+ readonly config: Record<string, unknown>;
398
+ /**
399
+ * Unique identifier for this addon layer instance.
400
+ * Useful for multi-instance scenarios.
401
+ */
402
+ readonly layerId: string;
403
+ /**
404
+ * SDK version. Always '2.0' for current API.
405
+ */
406
+ readonly version: '2.0';
407
+ /**
408
+ * Register callback for settings changes (hot-reload).
409
+ * Called when user modifies addon settings in the UI.
410
+ *
411
+ * @param callback - Function called with new settings and changed keys
412
+ */
413
+ onSettingsChange(callback: SettingsCallback): void;
414
+ /**
415
+ * Subscribe to a system event.
416
+ * @param event - Event type to subscribe to
417
+ * @param callback - Function called when event fires
418
+ */
419
+ onEvent<E extends SystemEventType>(event: E, callback: EventCallback<SystemEventData[E]>): void;
420
+ /**
421
+ * Unsubscribe from a system event.
422
+ * @param event - Event type to unsubscribe from
423
+ * @param callback - Same callback function passed to onEvent
424
+ */
425
+ offEvent<E extends SystemEventType>(event: E, callback: EventCallback<SystemEventData[E]>): void;
426
+ /**
427
+ * Called when addon is mounted (first load).
428
+ */
429
+ onMount(callback: () => void): void;
430
+ /**
431
+ * Called when addon is unmounted (removed from page).
432
+ * Use for cleanup (event listeners, timers, etc.).
433
+ */
434
+ onUnmount(callback: () => void): void;
435
+ /**
436
+ * Called when addon is paused (hidden, tab inactive).
437
+ * Use to pause animations and reduce resource usage.
438
+ */
439
+ onPause(callback: () => void): void;
440
+ /**
441
+ * Called when addon is resumed (visible again).
442
+ * Use to restart animations and refresh data.
443
+ */
444
+ onResume(callback: () => void): void;
445
+ /**
446
+ * Signal that the addon is ready and initialized.
447
+ * **Must be called** after your addon loads and initializes.
448
+ *
449
+ * @param options - Capabilities and event subscriptions
450
+ *
451
+ * @example
452
+ * ```typescript
453
+ * // After DOM is ready and addon is initialized
454
+ * window.MyWallpaper.ready({
455
+ * capabilities: ['hot-reload'],
456
+ * subscribedEvents: ['viewport:resize']
457
+ * })
458
+ * ```
459
+ */
460
+ ready(options?: ReadyOptions): void;
461
+ /**
462
+ * Signal that visual rendering is complete.
463
+ * Call after images are loaded, canvas drawn, animations ready.
464
+ * Used by screenshot service to capture addon at right moment.
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * async function init() {
469
+ * await loadAllImages()
470
+ * drawCanvas()
471
+ * window.MyWallpaper.renderComplete()
472
+ * }
473
+ * ```
474
+ */
475
+ renderComplete(): void;
476
+ /**
477
+ * Request a permission from the user.
478
+ * Shows a permission modal explaining why the permission is needed.
479
+ *
480
+ * @param permission - Permission type to request
481
+ * @param reason - User-friendly explanation of why permission is needed
482
+ * @returns Promise resolving to true if granted, false if denied
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * const granted = await window.MyWallpaper.requestPermission(
487
+ * 'storage',
488
+ * 'Save your preferences across sessions'
489
+ * )
490
+ * if (granted) {
491
+ * await window.MyWallpaper.storage.set('initialized', true)
492
+ * }
493
+ * ```
494
+ */
495
+ requestPermission(permission: PermissionType, reason: string): Promise<boolean>;
496
+ /**
497
+ * Persistent key-value storage API.
498
+ * Requires 'storage' permission to be granted first.
499
+ * Data syncs across devices for logged-in users.
500
+ */
501
+ storage: StorageAPI;
502
+ /**
503
+ * Network API for making HTTP requests through the secure host proxy.
504
+ * Requires 'network' permission and domains declared in manifest.
505
+ *
506
+ * **SECURITY:** Direct `fetch()` and `XMLHttpRequest` are blocked.
507
+ * This is the ONLY way to make network requests from addons.
508
+ *
509
+ * @example
510
+ * ```typescript
511
+ * // First, request permission
512
+ * const granted = await MyWallpaper.requestPermission('network', 'Fetch weather data')
513
+ * if (granted) {
514
+ * const response = await MyWallpaper.network.fetch('https://api.weather.com/current')
515
+ * if (response.ok) {
516
+ * console.log(response.data)
517
+ * }
518
+ * }
519
+ * ```
520
+ */
521
+ network: NetworkAPI;
522
+ /**
523
+ * OAuth API for making authenticated requests to provider APIs.
524
+ * Requires corresponding oauth permission (e.g., 'oauth:github').
525
+ *
526
+ * **SECURITY:** Your addon NEVER sees OAuth tokens. All requests are
527
+ * proxied through the backend which adds authentication server-side.
528
+ *
529
+ * **How it works:**
530
+ * 1. User connects their OAuth provider in MyWallpaper settings
531
+ * 2. Your addon requests permission (e.g., `oauth:github`)
532
+ * 3. API calls are proxied through the host
533
+ * 4. The host handles token refresh automatically
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * // First, request GitHub permission
538
+ * const granted = await MyWallpaper.requestPermission(
539
+ * 'oauth:github',
540
+ * 'Display your GitHub profile'
541
+ * )
542
+ *
543
+ * if (granted) {
544
+ * // Make authenticated API calls
545
+ * const response = await MyWallpaper.oauth.request('github', '/user')
546
+ * if (response.ok) {
547
+ * console.log('GitHub username:', response.data.login)
548
+ * }
549
+ * }
550
+ * ```
551
+ */
552
+ oauth: OAuthAPI;
553
+ }
554
+ /**
555
+ * Augments the global Window interface with MyWallpaper API.
556
+ * Allows `window.MyWallpaper` to be used without type assertions.
557
+ */
558
+ declare global {
559
+ interface Window {
560
+ /** Layer ID injected before addon loads */
561
+ __MYWALLPAPER_LAYER_ID__?: string;
562
+ /** Initial config injected before addon loads */
563
+ __MYWALLPAPER_INITIAL_CONFIG__?: Record<string, unknown>;
564
+ /** The main MyWallpaper API */
565
+ MyWallpaper: MyWallpaperAPI;
566
+ }
567
+ }
568
+
569
+ /**
570
+ * @mywallpaper/addon-sdk - Manifest Schema & Validation
571
+ *
572
+ * Defines the addon manifest format (manifest.json) and provides
573
+ * Zod-based validation for type safety at runtime.
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'
578
+ *
579
+ * const manifest: AddonManifest = {
580
+ * name: 'My Addon',
581
+ * version: '1.0.0',
582
+ * description: 'A cool addon',
583
+ * settings: {
584
+ * primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }
585
+ * }
586
+ * }
587
+ *
588
+ * // Validate at runtime
589
+ * const result = validateManifest(manifest)
590
+ * if (!result.success) {
591
+ * console.error('Invalid manifest:', result.errors)
592
+ * }
593
+ * ```
594
+ */
595
+
596
+ /**
597
+ * Available setting types for addon configuration.
598
+ */
599
+ type SettingType = 'string' | 'number' | 'boolean' | 'color' | 'select' | 'range' | 'image' | 'section' | 'textarea' | 'radio' | 'gradient' | 'vector2' | 'button' | 'multi-select';
600
+ /**
601
+ * Definition for a single addon setting.
602
+ *
603
+ * @example
604
+ * ```typescript
605
+ * const colorSetting: SettingDefinition = {
606
+ * type: 'color',
607
+ * label: 'Background Color',
608
+ * description: 'Choose a background color',
609
+ * default: '#000000'
610
+ * }
611
+ *
612
+ * const speedSetting: SettingDefinition = {
613
+ * type: 'range',
614
+ * label: 'Animation Speed',
615
+ * min: 0.1,
616
+ * max: 10,
617
+ * step: 0.1,
618
+ * default: 1
619
+ * }
620
+ * ```
621
+ */
622
+ interface SettingDefinition {
623
+ /** Setting type determines the UI control */
624
+ type: SettingType;
625
+ /** Human-readable label */
626
+ label?: string;
627
+ /** Help text shown below the control */
628
+ description?: string;
629
+ /** Default value (type depends on setting type) */
630
+ default?: unknown;
631
+ /** Options for select, radio, multi-select types */
632
+ options?: Array<{
633
+ value: string;
634
+ label: string;
635
+ }>;
636
+ /** Minimum value for range type */
637
+ min?: number;
638
+ /** Maximum value for range type */
639
+ max?: number;
640
+ /** Step increment for range type */
641
+ step?: number;
642
+ /** Label text on button */
643
+ buttonLabel?: string;
644
+ /** Axis labels for vector types, e.g., ['X', 'Y'] */
645
+ axisLabels?: [string, string] | [string, string, string];
646
+ /** Maximum number of selections */
647
+ maxItems?: number;
648
+ /** Only show if another setting has a specific value */
649
+ showIf?: {
650
+ setting: string;
651
+ equals: unknown;
652
+ };
653
+ }
654
+ /**
655
+ * Default layout configuration for addon positioning.
656
+ */
657
+ interface AddonDefaultLayout {
658
+ /** X position as percentage of viewport width (0-100) */
659
+ xPercent?: number;
660
+ /** Y position as percentage of viewport height (0-100) */
661
+ yPercent?: number;
662
+ /** Width as percentage of viewport width (0-100) */
663
+ widthPercent?: number;
664
+ /** Height as percentage of viewport height (0-100) */
665
+ heightPercent?: number;
666
+ /** Rotation in degrees */
667
+ rotation?: number;
668
+ }
669
+ /**
670
+ * The complete addon manifest schema.
671
+ * This defines your addon's identity, settings, and capabilities.
672
+ *
673
+ * @example
674
+ * ```json
675
+ * {
676
+ * "name": "Weather Widget",
677
+ * "version": "1.2.0",
678
+ * "description": "Displays current weather conditions",
679
+ * "author": "Jane Developer",
680
+ * "categories": ["utilities", "weather"],
681
+ * "capabilities": {
682
+ * "hotReload": true,
683
+ * "systemEvents": ["viewport:resize", "theme:change"]
684
+ * },
685
+ * "permissions": {
686
+ * "storage": { "quota": 512 },
687
+ * "network": { "domains": ["api.openweathermap.org"] }
688
+ * },
689
+ * "settings": {
690
+ * "apiKey": {
691
+ * "type": "string",
692
+ * "label": "API Key",
693
+ * "description": "Get your key at openweathermap.org"
694
+ * },
695
+ * "unit": {
696
+ * "type": "select",
697
+ * "label": "Temperature Unit",
698
+ * "default": "celsius",
699
+ * "options": [
700
+ * { "value": "celsius", "label": "Celsius" },
701
+ * { "value": "fahrenheit", "label": "Fahrenheit" }
702
+ * ]
703
+ * }
704
+ * }
705
+ * }
706
+ * ```
707
+ */
708
+ interface AddonManifest {
709
+ /** Addon name (displayed in UI) */
710
+ name: string;
711
+ /** Semantic version (e.g., "1.0.0") */
712
+ version: string;
713
+ /** Short description of the addon */
714
+ description?: string;
715
+ /** Author name or organization */
716
+ author?: string;
717
+ /** Addon type/category for filtering */
718
+ type?: string;
719
+ /** Categories for discoverability */
720
+ categories?: string[];
721
+ /** License (e.g., "MIT", "Apache-2.0") */
722
+ license?: string;
723
+ /** Homepage or documentation URL */
724
+ homepage?: string;
725
+ /** Repository URL */
726
+ repository?: string;
727
+ /**
728
+ * User-configurable settings.
729
+ * Key is the setting ID, value is the definition.
730
+ */
731
+ settings?: Record<string, SettingDefinition>;
732
+ /**
733
+ * Addon capabilities and event subscriptions.
734
+ */
735
+ capabilities?: {
736
+ /** Supports settings changes without full reload */
737
+ hotReload?: boolean;
738
+ /** System events the addon wants to receive */
739
+ systemEvents?: SystemEventType[];
740
+ };
741
+ /**
742
+ * Permissions the addon will request.
743
+ * Declaring them here improves UX by showing upfront.
744
+ */
745
+ permissions?: {
746
+ /** Storage permission with quota in KB */
747
+ storage?: {
748
+ quota: number;
749
+ };
750
+ /** CPU usage limit */
751
+ cpu?: {
752
+ limit: 'low' | 'medium' | 'high';
753
+ };
754
+ /** Network access with allowed domains */
755
+ network?: {
756
+ domains: string[];
757
+ };
758
+ /** Audio playback permission */
759
+ audio?: boolean;
760
+ /** Notification permission */
761
+ notifications?: boolean;
762
+ };
763
+ /**
764
+ * Default layout for automatic positioning.
765
+ * Used when addon is first added to a wallpaper.
766
+ */
767
+ defaultLayout?: AddonDefaultLayout;
768
+ /**
769
+ * OAuth providers and scopes for authenticated addons.
770
+ *
771
+ * Declare the OAuth providers your addon needs and the scopes it requires.
772
+ * Scopes are requested incrementally - users can connect with basic scopes
773
+ * first, and your addon can request additional scopes when needed.
774
+ *
775
+ * @example
776
+ * ```json
777
+ * {
778
+ * "permissions": {
779
+ * "oauth": {
780
+ * "required": [
781
+ * { "provider": "github", "scopes": ["read:user"] }
782
+ * ],
783
+ * "optional": [
784
+ * { "provider": "github", "scopes": ["repo"] }
785
+ * ]
786
+ * }
787
+ * }
788
+ * }
789
+ * ```
790
+ */
791
+ oauth?: {
792
+ /** OAuth providers required for the addon to function */
793
+ required?: Array<{
794
+ provider: OAuthProvider;
795
+ scopes: string[];
796
+ /** Reason shown to user when requesting this permission */
797
+ reason?: string;
798
+ }>;
799
+ /** OAuth providers that enhance functionality but aren't required */
800
+ optional?: Array<{
801
+ provider: OAuthProvider;
802
+ scopes: string[];
803
+ /** Reason shown to user when requesting this permission */
804
+ reason?: string;
805
+ }>;
806
+ };
807
+ }
808
+ /**
809
+ * Validation result type.
810
+ */
811
+ interface ValidationResult {
812
+ success: boolean;
813
+ errors?: string[];
814
+ manifest?: AddonManifest;
815
+ }
816
+ /**
817
+ * Validates an addon manifest object.
818
+ *
819
+ * @param input - The manifest object to validate
820
+ * @returns ValidationResult with success status and any errors
821
+ *
822
+ * @example
823
+ * ```typescript
824
+ * const result = validateManifest(myManifest)
825
+ * if (!result.success) {
826
+ * result.errors?.forEach(err => console.error(err))
827
+ * }
828
+ * ```
829
+ */
830
+ declare function validateManifest(input: unknown): ValidationResult;
831
+ /**
832
+ * Type guard to check if an object is a valid AddonManifest.
833
+ *
834
+ * @param input - Object to check
835
+ * @returns True if input is a valid AddonManifest
836
+ */
837
+ declare function isValidManifest(input: unknown): input is AddonManifest;
838
+ /**
839
+ * Check if manifest declares hot-reload capability.
840
+ */
841
+ declare function supportsHotReload(manifest: AddonManifest): boolean;
842
+ /**
843
+ * Get system events the addon subscribes to.
844
+ */
845
+ declare function getSubscribedEvents(manifest: AddonManifest): SystemEventType[];
846
+ /**
847
+ * Get required permissions from manifest.
848
+ */
849
+ declare function getRequiredPermissions(manifest: AddonManifest): PermissionType[];
850
+
851
+ export { type AddonDefaultLayout, type AddonManifest, type SettingDefinition, type SettingType, type ValidationResult, getRequiredPermissions, getSubscribedEvents, isValidManifest, supportsHotReload, validateManifest };