@involvex/youtube-music-cli 0.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.
Files changed (136) hide show
  1. package/README.md +352 -0
  2. package/dist/eslint.config.d.ts +2 -0
  3. package/dist/eslint.config.js +55 -0
  4. package/dist/source/app.d.ts +4 -0
  5. package/dist/source/app.js +17 -0
  6. package/dist/source/cli.d.ts +2 -0
  7. package/dist/source/cli.js +241 -0
  8. package/dist/source/components/common/ErrorBoundary.d.ts +15 -0
  9. package/dist/source/components/common/ErrorBoundary.js +22 -0
  10. package/dist/source/components/common/Help.d.ts +1 -0
  11. package/dist/source/components/common/Help.js +10 -0
  12. package/dist/source/components/common/ShortcutsBar.d.ts +1 -0
  13. package/dist/source/components/common/ShortcutsBar.js +33 -0
  14. package/dist/source/components/config/ConfigLayout.d.ts +1 -0
  15. package/dist/source/components/config/ConfigLayout.js +84 -0
  16. package/dist/source/components/layouts/MainLayout.d.ts +4 -0
  17. package/dist/source/components/layouts/MainLayout.js +83 -0
  18. package/dist/source/components/layouts/PlayerLayout.d.ts +1 -0
  19. package/dist/source/components/layouts/PlayerLayout.js +10 -0
  20. package/dist/source/components/layouts/PluginsLayout.d.ts +1 -0
  21. package/dist/source/components/layouts/PluginsLayout.js +77 -0
  22. package/dist/source/components/layouts/SearchLayout.d.ts +4 -0
  23. package/dist/source/components/layouts/SearchLayout.js +81 -0
  24. package/dist/source/components/player/NowPlaying.d.ts +1 -0
  25. package/dist/source/components/player/NowPlaying.js +21 -0
  26. package/dist/source/components/player/PlayerControls.d.ts +1 -0
  27. package/dist/source/components/player/PlayerControls.js +41 -0
  28. package/dist/source/components/player/ProgressBar.d.ts +1 -0
  29. package/dist/source/components/player/ProgressBar.js +18 -0
  30. package/dist/source/components/player/QueueList.d.ts +4 -0
  31. package/dist/source/components/player/QueueList.js +30 -0
  32. package/dist/source/components/player/Suggestions.d.ts +1 -0
  33. package/dist/source/components/player/Suggestions.js +47 -0
  34. package/dist/source/components/playlist/PlaylistList.d.ts +1 -0
  35. package/dist/source/components/playlist/PlaylistList.js +11 -0
  36. package/dist/source/components/plugins/PluginInstallDialog.d.ts +5 -0
  37. package/dist/source/components/plugins/PluginInstallDialog.js +41 -0
  38. package/dist/source/components/plugins/PluginsAvailable.d.ts +5 -0
  39. package/dist/source/components/plugins/PluginsAvailable.js +55 -0
  40. package/dist/source/components/plugins/PluginsList.d.ts +8 -0
  41. package/dist/source/components/plugins/PluginsList.js +18 -0
  42. package/dist/source/components/search/SearchBar.d.ts +8 -0
  43. package/dist/source/components/search/SearchBar.js +50 -0
  44. package/dist/source/components/search/SearchResults.d.ts +10 -0
  45. package/dist/source/components/search/SearchResults.js +111 -0
  46. package/dist/source/components/settings/Settings.d.ts +1 -0
  47. package/dist/source/components/settings/Settings.js +42 -0
  48. package/dist/source/components/theme/ThemeSwitcher.d.ts +1 -0
  49. package/dist/source/components/theme/ThemeSwitcher.js +11 -0
  50. package/dist/source/config/themes.config.d.ts +3 -0
  51. package/dist/source/config/themes.config.js +63 -0
  52. package/dist/source/contexts/theme.context.d.ts +13 -0
  53. package/dist/source/contexts/theme.context.js +29 -0
  54. package/dist/source/hooks/useKeyboard.d.ts +10 -0
  55. package/dist/source/hooks/useKeyboard.js +104 -0
  56. package/dist/source/hooks/useNavigation.d.ts +1 -0
  57. package/dist/source/hooks/useNavigation.js +5 -0
  58. package/dist/source/hooks/usePlayer.d.ts +23 -0
  59. package/dist/source/hooks/usePlayer.js +35 -0
  60. package/dist/source/hooks/usePlaylist.d.ts +8 -0
  61. package/dist/source/hooks/usePlaylist.js +50 -0
  62. package/dist/source/hooks/useSearch.d.ts +8 -0
  63. package/dist/source/hooks/useSearch.js +76 -0
  64. package/dist/source/hooks/useTerminalSize.d.ts +4 -0
  65. package/dist/source/hooks/useTerminalSize.js +24 -0
  66. package/dist/source/hooks/useTheme.d.ts +6 -0
  67. package/dist/source/hooks/useTheme.js +5 -0
  68. package/dist/source/hooks/useYouTubeMusic.d.ts +11 -0
  69. package/dist/source/hooks/useYouTubeMusic.js +112 -0
  70. package/dist/source/main.d.ts +4 -0
  71. package/dist/source/main.js +69 -0
  72. package/dist/source/services/config/config.service.d.ts +26 -0
  73. package/dist/source/services/config/config.service.js +125 -0
  74. package/dist/source/services/logger/logger.service.d.ts +10 -0
  75. package/dist/source/services/logger/logger.service.js +52 -0
  76. package/dist/source/services/player/player.service.d.ts +58 -0
  77. package/dist/source/services/player/player.service.js +349 -0
  78. package/dist/source/services/player-state/player-state.service.d.ts +24 -0
  79. package/dist/source/services/player-state/player-state.service.js +122 -0
  80. package/dist/source/services/plugin/plugin-audio-api.d.ts +17 -0
  81. package/dist/source/services/plugin/plugin-audio-api.js +36 -0
  82. package/dist/source/services/plugin/plugin-context.d.ts +5 -0
  83. package/dist/source/services/plugin/plugin-context.js +256 -0
  84. package/dist/source/services/plugin/plugin-hooks.service.d.ts +62 -0
  85. package/dist/source/services/plugin/plugin-hooks.service.js +135 -0
  86. package/dist/source/services/plugin/plugin-installer.service.d.ts +27 -0
  87. package/dist/source/services/plugin/plugin-installer.service.js +247 -0
  88. package/dist/source/services/plugin/plugin-loader.service.d.ts +33 -0
  89. package/dist/source/services/plugin/plugin-loader.service.js +161 -0
  90. package/dist/source/services/plugin/plugin-permissions.service.d.ts +72 -0
  91. package/dist/source/services/plugin/plugin-permissions.service.js +194 -0
  92. package/dist/source/services/plugin/plugin-registry.service.d.ts +76 -0
  93. package/dist/source/services/plugin/plugin-registry.service.js +215 -0
  94. package/dist/source/services/plugin/plugin-ui-api.d.ts +25 -0
  95. package/dist/source/services/plugin/plugin-ui-api.js +46 -0
  96. package/dist/source/services/plugin/plugin-updater.service.d.ts +23 -0
  97. package/dist/source/services/plugin/plugin-updater.service.js +206 -0
  98. package/dist/source/services/youtube-music/api.d.ts +13 -0
  99. package/dist/source/services/youtube-music/api.js +371 -0
  100. package/dist/source/services/youtube-music/search.service.d.ts +11 -0
  101. package/dist/source/services/youtube-music/search.service.js +38 -0
  102. package/dist/source/stores/navigation.store.d.ts +10 -0
  103. package/dist/source/stores/navigation.store.js +67 -0
  104. package/dist/source/stores/player.store.d.ts +28 -0
  105. package/dist/source/stores/player.store.js +458 -0
  106. package/dist/source/stores/plugins.store.d.ts +46 -0
  107. package/dist/source/stores/plugins.store.js +177 -0
  108. package/dist/source/types/actions.d.ts +119 -0
  109. package/dist/source/types/actions.js +1 -0
  110. package/dist/source/types/cli.types.d.ts +14 -0
  111. package/dist/source/types/cli.types.js +1 -0
  112. package/dist/source/types/config.types.d.ts +19 -0
  113. package/dist/source/types/config.types.js +1 -0
  114. package/dist/source/types/keyboard.types.d.ts +5 -0
  115. package/dist/source/types/keyboard.types.js +1 -0
  116. package/dist/source/types/navigation.types.d.ts +14 -0
  117. package/dist/source/types/navigation.types.js +1 -0
  118. package/dist/source/types/player.types.d.ts +16 -0
  119. package/dist/source/types/player.types.js +1 -0
  120. package/dist/source/types/playlist.types.d.ts +12 -0
  121. package/dist/source/types/playlist.types.js +1 -0
  122. package/dist/source/types/plugin.types.d.ts +239 -0
  123. package/dist/source/types/plugin.types.js +1 -0
  124. package/dist/source/types/theme.types.d.ts +18 -0
  125. package/dist/source/types/theme.types.js +1 -0
  126. package/dist/source/types/youtube-music.types.d.ts +35 -0
  127. package/dist/source/types/youtube-music.types.js +1 -0
  128. package/dist/source/types/youtubei.types.d.ts +60 -0
  129. package/dist/source/types/youtubei.types.js +3 -0
  130. package/dist/source/utils/constants.d.ts +65 -0
  131. package/dist/source/utils/constants.js +82 -0
  132. package/dist/source/utils/format.d.ts +3 -0
  133. package/dist/source/utils/format.js +24 -0
  134. package/dist/test.d.ts +1 -0
  135. package/dist/test.js +13 -0
  136. package/package.json +100 -0
@@ -0,0 +1,194 @@
1
+ import { CONFIG_DIR } from "../../utils/constants.js";
2
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
3
+ import { logger } from "../logger/logger.service.js";
4
+ import { join } from 'node:path';
5
+ const PERMISSIONS_FILE = join(CONFIG_DIR, 'plugin-permissions.json');
6
+ /**
7
+ * Plugin permissions service - manages permission grants and denials
8
+ */
9
+ class PluginPermissionsService {
10
+ permissions;
11
+ permissionsPath;
12
+ configDir;
13
+ // Permission request callback (can be overridden for testing or custom UI)
14
+ onPermissionRequest;
15
+ constructor() {
16
+ this.configDir = CONFIG_DIR;
17
+ this.permissionsPath = PERMISSIONS_FILE;
18
+ this.permissions = this.load();
19
+ }
20
+ /**
21
+ * Load permissions from disk
22
+ */
23
+ load() {
24
+ try {
25
+ if (!existsSync(this.permissionsPath)) {
26
+ return {};
27
+ }
28
+ const data = readFileSync(this.permissionsPath, 'utf-8');
29
+ return JSON.parse(data);
30
+ }
31
+ catch (error) {
32
+ logger.error('PluginPermissionsService', 'Failed to load permissions:', error);
33
+ return {};
34
+ }
35
+ }
36
+ /**
37
+ * Save permissions to disk
38
+ */
39
+ save() {
40
+ try {
41
+ // Ensure config directory exists
42
+ if (!existsSync(this.configDir)) {
43
+ mkdirSync(this.configDir, { recursive: true });
44
+ }
45
+ writeFileSync(this.permissionsPath, JSON.stringify(this.permissions, null, 2));
46
+ logger.debug('PluginPermissionsService', 'Saved permissions to disk');
47
+ }
48
+ catch (error) {
49
+ logger.error('PluginPermissionsService', 'Failed to save permissions:', error);
50
+ }
51
+ }
52
+ /**
53
+ * Check if a plugin has a specific permission
54
+ */
55
+ hasPermission(pluginId, permission) {
56
+ const pluginPerms = this.permissions[pluginId];
57
+ if (!pluginPerms) {
58
+ return false;
59
+ }
60
+ return pluginPerms[permission] === 'granted';
61
+ }
62
+ /**
63
+ * Get permission status
64
+ */
65
+ getPermissionStatus(pluginId, permission) {
66
+ const pluginPerms = this.permissions[pluginId];
67
+ if (!pluginPerms || !pluginPerms[permission]) {
68
+ return 'prompt';
69
+ }
70
+ return pluginPerms[permission];
71
+ }
72
+ /**
73
+ * Get all permissions for a plugin
74
+ */
75
+ getPermissions(pluginId) {
76
+ return this.permissions[pluginId] || {};
77
+ }
78
+ /**
79
+ * Grant a permission to a plugin
80
+ */
81
+ grantPermission(pluginId, permission) {
82
+ if (!this.permissions[pluginId]) {
83
+ this.permissions[pluginId] = {};
84
+ }
85
+ this.permissions[pluginId][permission] = 'granted';
86
+ this.save();
87
+ logger.info('PluginPermissionsService', `Granted ${permission} to ${pluginId}`);
88
+ }
89
+ /**
90
+ * Deny a permission to a plugin
91
+ */
92
+ denyPermission(pluginId, permission) {
93
+ if (!this.permissions[pluginId]) {
94
+ this.permissions[pluginId] = {};
95
+ }
96
+ this.permissions[pluginId][permission] = 'denied';
97
+ this.save();
98
+ logger.info('PluginPermissionsService', `Denied ${permission} to ${pluginId}`);
99
+ }
100
+ /**
101
+ * Request permission from user
102
+ */
103
+ async requestPermission(pluginId, permission) {
104
+ const currentStatus = this.getPermissionStatus(pluginId, permission);
105
+ // If already granted or denied, return cached result
106
+ if (currentStatus === 'granted') {
107
+ return true;
108
+ }
109
+ if (currentStatus === 'denied') {
110
+ return false;
111
+ }
112
+ // Prompt user
113
+ if (this.onPermissionRequest) {
114
+ try {
115
+ const granted = await this.onPermissionRequest(pluginId, permission);
116
+ if (granted) {
117
+ this.grantPermission(pluginId, permission);
118
+ }
119
+ else {
120
+ this.denyPermission(pluginId, permission);
121
+ }
122
+ return granted;
123
+ }
124
+ catch (error) {
125
+ logger.error('PluginPermissionsService', 'Error requesting permission:', error);
126
+ // Default to deny on error
127
+ this.denyPermission(pluginId, permission);
128
+ return false;
129
+ }
130
+ }
131
+ // No callback registered - default to deny for security
132
+ logger.warn('PluginPermissionsService', `No permission request handler, denying ${permission} for ${pluginId}`);
133
+ this.denyPermission(pluginId, permission);
134
+ return false;
135
+ }
136
+ /**
137
+ * Grant multiple permissions at once
138
+ */
139
+ grantPermissions(pluginId, permissions) {
140
+ for (const permission of permissions) {
141
+ this.grantPermission(pluginId, permission);
142
+ }
143
+ }
144
+ /**
145
+ * Revoke a permission
146
+ */
147
+ revokePermission(pluginId, permission) {
148
+ if (!this.permissions[pluginId]) {
149
+ return;
150
+ }
151
+ delete this.permissions[pluginId][permission];
152
+ this.save();
153
+ logger.info('PluginPermissionsService', `Revoked ${permission} from ${pluginId}`);
154
+ }
155
+ /**
156
+ * Revoke all permissions for a plugin
157
+ */
158
+ revokeAllPermissions(pluginId) {
159
+ delete this.permissions[pluginId];
160
+ this.save();
161
+ logger.info('PluginPermissionsService', `Revoked all permissions from ${pluginId}`);
162
+ }
163
+ /**
164
+ * Get all plugin IDs with permissions
165
+ */
166
+ getAllPluginIds() {
167
+ return Object.keys(this.permissions);
168
+ }
169
+ /**
170
+ * Reset all permissions (for testing or user request)
171
+ */
172
+ resetAll() {
173
+ this.permissions = {};
174
+ this.save();
175
+ logger.info('PluginPermissionsService', 'Reset all permissions');
176
+ }
177
+ }
178
+ // Singleton instance
179
+ let instance = null;
180
+ /**
181
+ * Get the plugin permissions service singleton
182
+ */
183
+ export function getPluginPermissionsService() {
184
+ if (!instance) {
185
+ instance = new PluginPermissionsService();
186
+ }
187
+ return instance;
188
+ }
189
+ /**
190
+ * Reset the singleton (for testing)
191
+ */
192
+ export function resetPluginPermissionsService() {
193
+ instance = null;
194
+ }
@@ -0,0 +1,76 @@
1
+ import type { PluginInstance, PluginPermissions } from '../../types/plugin.types.ts';
2
+ /**
3
+ * Plugin registry service - manages all loaded plugins
4
+ */
5
+ declare class PluginRegistryService {
6
+ private plugins;
7
+ private pluginLoader;
8
+ private permissionsService;
9
+ private configService;
10
+ constructor();
11
+ /**
12
+ * Load a plugin from a directory
13
+ */
14
+ loadPlugin(pluginPath: string): Promise<PluginInstance>;
15
+ /**
16
+ * Unload a plugin
17
+ */
18
+ unloadPlugin(pluginId: string): Promise<void>;
19
+ /**
20
+ * Enable a plugin
21
+ */
22
+ enablePlugin(pluginId: string): Promise<void>;
23
+ /**
24
+ * Disable a plugin
25
+ */
26
+ disablePlugin(pluginId: string): Promise<void>;
27
+ /**
28
+ * Get a plugin instance
29
+ */
30
+ getPlugin(pluginId: string): PluginInstance | undefined;
31
+ /**
32
+ * Get all plugins
33
+ */
34
+ getAllPlugins(): PluginInstance[];
35
+ /**
36
+ * Get enabled plugins
37
+ */
38
+ getEnabledPlugins(): PluginInstance[];
39
+ /**
40
+ * Check if a plugin is loaded
41
+ */
42
+ isLoaded(pluginId: string): boolean;
43
+ /**
44
+ * Check if a plugin is enabled
45
+ */
46
+ isEnabled(pluginId: string): boolean;
47
+ /**
48
+ * Get plugin permissions
49
+ */
50
+ getPermissions(pluginId: string): PluginPermissions;
51
+ /**
52
+ * Load all plugins from the plugins directory
53
+ */
54
+ loadAllPlugins(): Promise<void>;
55
+ /**
56
+ * Save plugin enabled/disabled state to config
57
+ */
58
+ private savePluginState;
59
+ /**
60
+ * Get saved plugin state from config
61
+ */
62
+ private getSavedPluginState;
63
+ /**
64
+ * Clear all plugins
65
+ */
66
+ unloadAllPlugins(): Promise<void>;
67
+ }
68
+ /**
69
+ * Get the plugin registry service singleton
70
+ */
71
+ export declare function getPluginRegistryService(): PluginRegistryService;
72
+ /**
73
+ * Reset the singleton (for testing)
74
+ */
75
+ export declare function resetPluginRegistryService(): void;
76
+ export {};
@@ -0,0 +1,215 @@
1
+ import { getPluginLoaderService } from "./plugin-loader.service.js";
2
+ import { getPluginPermissionsService } from "./plugin-permissions.service.js";
3
+ import { getConfigService } from "../config/config.service.js";
4
+ import { logger } from "../logger/logger.service.js";
5
+ import { CONFIG_DIR } from "../../utils/constants.js";
6
+ import { join } from 'node:path';
7
+ import { existsSync, readdirSync } from 'node:fs';
8
+ const PLUGINS_DIR = join(CONFIG_DIR, 'plugins');
9
+ /**
10
+ * Plugin registry service - manages all loaded plugins
11
+ */
12
+ class PluginRegistryService {
13
+ plugins;
14
+ pluginLoader = getPluginLoaderService();
15
+ permissionsService = getPluginPermissionsService();
16
+ configService = getConfigService();
17
+ constructor() {
18
+ this.plugins = new Map();
19
+ }
20
+ /**
21
+ * Load a plugin from a directory
22
+ */
23
+ async loadPlugin(pluginPath) {
24
+ const instance = await this.pluginLoader.loadPlugin(pluginPath);
25
+ // Check if already loaded
26
+ if (this.plugins.has(instance.manifest.id)) {
27
+ throw new Error(`Plugin ${instance.manifest.id} is already loaded`);
28
+ }
29
+ // Store in registry
30
+ this.plugins.set(instance.manifest.id, instance);
31
+ logger.info('PluginRegistryService', `Registered plugin: ${instance.manifest.name}`);
32
+ return instance;
33
+ }
34
+ /**
35
+ * Unload a plugin
36
+ */
37
+ async unloadPlugin(pluginId) {
38
+ const instance = this.plugins.get(pluginId);
39
+ if (!instance) {
40
+ throw new Error(`Plugin ${pluginId} is not loaded`);
41
+ }
42
+ // Call destroy hook if enabled
43
+ if (instance.enabled && instance.plugin.destroy) {
44
+ try {
45
+ await this.pluginLoader.callHook(instance.plugin, 'destroy', instance.context);
46
+ }
47
+ catch (error) {
48
+ logger.error('PluginRegistryService', `Error destroying plugin ${pluginId}:`, error);
49
+ }
50
+ }
51
+ // Remove from registry
52
+ this.plugins.delete(pluginId);
53
+ logger.info('PluginRegistryService', `Unloaded plugin: ${pluginId}`);
54
+ }
55
+ /**
56
+ * Enable a plugin
57
+ */
58
+ async enablePlugin(pluginId) {
59
+ const instance = this.plugins.get(pluginId);
60
+ if (!instance) {
61
+ throw new Error(`Plugin ${pluginId} is not loaded`);
62
+ }
63
+ if (instance.enabled) {
64
+ logger.debug('PluginRegistryService', `Plugin ${pluginId} is already enabled`);
65
+ return;
66
+ }
67
+ // Call enable hook
68
+ if (instance.plugin.enable) {
69
+ await this.pluginLoader.callHook(instance.plugin, 'enable', instance.context);
70
+ }
71
+ instance.enabled = true;
72
+ instance.config.enabled = true;
73
+ this.savePluginState();
74
+ logger.info('PluginRegistryService', `Enabled plugin: ${pluginId}`);
75
+ }
76
+ /**
77
+ * Disable a plugin
78
+ */
79
+ async disablePlugin(pluginId) {
80
+ const instance = this.plugins.get(pluginId);
81
+ if (!instance) {
82
+ throw new Error(`Plugin ${pluginId} is not loaded`);
83
+ }
84
+ if (!instance.enabled) {
85
+ logger.debug('PluginRegistryService', `Plugin ${pluginId} is already disabled`);
86
+ return;
87
+ }
88
+ // Call disable hook
89
+ if (instance.plugin.disable) {
90
+ await this.pluginLoader.callHook(instance.plugin, 'disable', instance.context);
91
+ }
92
+ instance.enabled = false;
93
+ instance.config.enabled = false;
94
+ this.savePluginState();
95
+ logger.info('PluginRegistryService', `Disabled plugin: ${pluginId}`);
96
+ }
97
+ /**
98
+ * Get a plugin instance
99
+ */
100
+ getPlugin(pluginId) {
101
+ return this.plugins.get(pluginId);
102
+ }
103
+ /**
104
+ * Get all plugins
105
+ */
106
+ getAllPlugins() {
107
+ return [...this.plugins.values()];
108
+ }
109
+ /**
110
+ * Get enabled plugins
111
+ */
112
+ getEnabledPlugins() {
113
+ return this.getAllPlugins().filter(p => p.enabled);
114
+ }
115
+ /**
116
+ * Check if a plugin is loaded
117
+ */
118
+ isLoaded(pluginId) {
119
+ return this.plugins.has(pluginId);
120
+ }
121
+ /**
122
+ * Check if a plugin is enabled
123
+ */
124
+ isEnabled(pluginId) {
125
+ const plugin = this.plugins.get(pluginId);
126
+ return plugin?.enabled ?? false;
127
+ }
128
+ /**
129
+ * Get plugin permissions
130
+ */
131
+ getPermissions(pluginId) {
132
+ return this.permissionsService.getPermissions(pluginId);
133
+ }
134
+ /**
135
+ * Load all plugins from the plugins directory
136
+ */
137
+ async loadAllPlugins() {
138
+ if (!existsSync(PLUGINS_DIR)) {
139
+ logger.info('PluginRegistryService', 'Plugins directory does not exist, skipping plugin loading');
140
+ return;
141
+ }
142
+ const entries = readdirSync(PLUGINS_DIR, { withFileTypes: true });
143
+ const pluginDirs = entries
144
+ .filter(entry => entry.isDirectory())
145
+ .map(entry => join(PLUGINS_DIR, entry.name));
146
+ logger.info('PluginRegistryService', `Found ${pluginDirs.length} potential plugin(s)`);
147
+ for (const pluginDir of pluginDirs) {
148
+ try {
149
+ const instance = await this.loadPlugin(pluginDir);
150
+ // Check if plugin was previously enabled
151
+ const savedState = this.getSavedPluginState(instance.manifest.id);
152
+ if (savedState?.enabled) {
153
+ await this.enablePlugin(instance.manifest.id);
154
+ }
155
+ }
156
+ catch (error) {
157
+ logger.error('PluginRegistryService', `Failed to load plugin from ${pluginDir}:`, error);
158
+ }
159
+ }
160
+ logger.info('PluginRegistryService', `Loaded ${this.plugins.size} plugin(s)`);
161
+ }
162
+ /**
163
+ * Save plugin enabled/disabled state to config
164
+ */
165
+ savePluginState() {
166
+ const pluginStates = {};
167
+ for (const [id, instance] of this.plugins) {
168
+ pluginStates[id] = { enabled: instance.enabled };
169
+ }
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ this.configService.set('pluginStates', pluginStates);
172
+ }
173
+ /**
174
+ * Get saved plugin state from config
175
+ */
176
+ getSavedPluginState(pluginId) {
177
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
178
+ const states = this.configService.get('pluginStates');
179
+ return states?.[pluginId];
180
+ }
181
+ /**
182
+ * Clear all plugins
183
+ */
184
+ async unloadAllPlugins() {
185
+ const pluginIds = [...this.plugins.keys()];
186
+ for (const pluginId of pluginIds) {
187
+ try {
188
+ await this.unloadPlugin(pluginId);
189
+ }
190
+ catch (error) {
191
+ logger.error('PluginRegistryService', `Error unloading plugin ${pluginId}:`, error);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ // Singleton instance
197
+ let instance = null;
198
+ /**
199
+ * Get the plugin registry service singleton
200
+ */
201
+ export function getPluginRegistryService() {
202
+ if (!instance) {
203
+ instance = new PluginRegistryService();
204
+ }
205
+ return instance;
206
+ }
207
+ /**
208
+ * Reset the singleton (for testing)
209
+ */
210
+ export function resetPluginRegistryService() {
211
+ if (instance) {
212
+ void instance.unloadAllPlugins();
213
+ }
214
+ instance = null;
215
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactElement } from 'react';
2
+ /**
3
+ * Register a plugin view
4
+ */
5
+ export declare function registerPluginView(viewId: string, component: ReactElement): void;
6
+ /**
7
+ * Unregister a plugin view
8
+ */
9
+ export declare function unregisterPluginView(viewId: string): void;
10
+ /**
11
+ * Get a plugin view by ID
12
+ */
13
+ export declare function getPluginView(viewId: string): ReactElement | undefined;
14
+ /**
15
+ * Check if a plugin view exists
16
+ */
17
+ export declare function hasPluginView(viewId: string): boolean;
18
+ /**
19
+ * Get all registered plugin views
20
+ */
21
+ export declare function getAllPluginViews(): Map<string, ReactElement>;
22
+ /**
23
+ * Clear all plugin views (for cleanup)
24
+ */
25
+ export declare function clearAllPluginViews(): void;
@@ -0,0 +1,46 @@
1
+ import { logger } from "../logger/logger.service.js";
2
+ // Registry for plugin views
3
+ const pluginViews = new Map();
4
+ /**
5
+ * Register a plugin view
6
+ */
7
+ export function registerPluginView(viewId, component) {
8
+ if (pluginViews.has(viewId)) {
9
+ logger.warn('PluginUIAPI', `View ${viewId} is already registered`);
10
+ return;
11
+ }
12
+ pluginViews.set(viewId, component);
13
+ logger.info('PluginUIAPI', `Registered view: ${viewId}`);
14
+ }
15
+ /**
16
+ * Unregister a plugin view
17
+ */
18
+ export function unregisterPluginView(viewId) {
19
+ pluginViews.delete(viewId);
20
+ logger.info('PluginUIAPI', `Unregistered view: ${viewId}`);
21
+ }
22
+ /**
23
+ * Get a plugin view by ID
24
+ */
25
+ export function getPluginView(viewId) {
26
+ return pluginViews.get(viewId);
27
+ }
28
+ /**
29
+ * Check if a plugin view exists
30
+ */
31
+ export function hasPluginView(viewId) {
32
+ return pluginViews.has(viewId);
33
+ }
34
+ /**
35
+ * Get all registered plugin views
36
+ */
37
+ export function getAllPluginViews() {
38
+ return new Map(pluginViews);
39
+ }
40
+ /**
41
+ * Clear all plugin views (for cleanup)
42
+ */
43
+ export function clearAllPluginViews() {
44
+ pluginViews.clear();
45
+ logger.info('PluginUIAPI', 'Cleared all plugin views');
46
+ }
@@ -0,0 +1,23 @@
1
+ import type { PluginUpdateResult } from '../../types/plugin.types.ts';
2
+ /**
3
+ * Plugin updater service
4
+ */
5
+ declare class PluginUpdaterService {
6
+ /**
7
+ * Check if updates are available for a plugin
8
+ */
9
+ checkForUpdates(pluginId: string): Promise<{
10
+ hasUpdate: boolean;
11
+ currentVersion?: string;
12
+ latestVersion?: string;
13
+ }>;
14
+ /**
15
+ * Update a plugin with smart merge (preserve user data)
16
+ */
17
+ updatePlugin(pluginId: string): Promise<PluginUpdateResult>;
18
+ }
19
+ /**
20
+ * Get the plugin updater service singleton
21
+ */
22
+ export declare function getPluginUpdaterService(): PluginUpdaterService;
23
+ export {};