@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.
- package/README.md +352 -0
- package/dist/eslint.config.d.ts +2 -0
- package/dist/eslint.config.js +55 -0
- package/dist/source/app.d.ts +4 -0
- package/dist/source/app.js +17 -0
- package/dist/source/cli.d.ts +2 -0
- package/dist/source/cli.js +241 -0
- package/dist/source/components/common/ErrorBoundary.d.ts +15 -0
- package/dist/source/components/common/ErrorBoundary.js +22 -0
- package/dist/source/components/common/Help.d.ts +1 -0
- package/dist/source/components/common/Help.js +10 -0
- package/dist/source/components/common/ShortcutsBar.d.ts +1 -0
- package/dist/source/components/common/ShortcutsBar.js +33 -0
- package/dist/source/components/config/ConfigLayout.d.ts +1 -0
- package/dist/source/components/config/ConfigLayout.js +84 -0
- package/dist/source/components/layouts/MainLayout.d.ts +4 -0
- package/dist/source/components/layouts/MainLayout.js +83 -0
- package/dist/source/components/layouts/PlayerLayout.d.ts +1 -0
- package/dist/source/components/layouts/PlayerLayout.js +10 -0
- package/dist/source/components/layouts/PluginsLayout.d.ts +1 -0
- package/dist/source/components/layouts/PluginsLayout.js +77 -0
- package/dist/source/components/layouts/SearchLayout.d.ts +4 -0
- package/dist/source/components/layouts/SearchLayout.js +81 -0
- package/dist/source/components/player/NowPlaying.d.ts +1 -0
- package/dist/source/components/player/NowPlaying.js +21 -0
- package/dist/source/components/player/PlayerControls.d.ts +1 -0
- package/dist/source/components/player/PlayerControls.js +41 -0
- package/dist/source/components/player/ProgressBar.d.ts +1 -0
- package/dist/source/components/player/ProgressBar.js +18 -0
- package/dist/source/components/player/QueueList.d.ts +4 -0
- package/dist/source/components/player/QueueList.js +30 -0
- package/dist/source/components/player/Suggestions.d.ts +1 -0
- package/dist/source/components/player/Suggestions.js +47 -0
- package/dist/source/components/playlist/PlaylistList.d.ts +1 -0
- package/dist/source/components/playlist/PlaylistList.js +11 -0
- package/dist/source/components/plugins/PluginInstallDialog.d.ts +5 -0
- package/dist/source/components/plugins/PluginInstallDialog.js +41 -0
- package/dist/source/components/plugins/PluginsAvailable.d.ts +5 -0
- package/dist/source/components/plugins/PluginsAvailable.js +55 -0
- package/dist/source/components/plugins/PluginsList.d.ts +8 -0
- package/dist/source/components/plugins/PluginsList.js +18 -0
- package/dist/source/components/search/SearchBar.d.ts +8 -0
- package/dist/source/components/search/SearchBar.js +50 -0
- package/dist/source/components/search/SearchResults.d.ts +10 -0
- package/dist/source/components/search/SearchResults.js +111 -0
- package/dist/source/components/settings/Settings.d.ts +1 -0
- package/dist/source/components/settings/Settings.js +42 -0
- package/dist/source/components/theme/ThemeSwitcher.d.ts +1 -0
- package/dist/source/components/theme/ThemeSwitcher.js +11 -0
- package/dist/source/config/themes.config.d.ts +3 -0
- package/dist/source/config/themes.config.js +63 -0
- package/dist/source/contexts/theme.context.d.ts +13 -0
- package/dist/source/contexts/theme.context.js +29 -0
- package/dist/source/hooks/useKeyboard.d.ts +10 -0
- package/dist/source/hooks/useKeyboard.js +104 -0
- package/dist/source/hooks/useNavigation.d.ts +1 -0
- package/dist/source/hooks/useNavigation.js +5 -0
- package/dist/source/hooks/usePlayer.d.ts +23 -0
- package/dist/source/hooks/usePlayer.js +35 -0
- package/dist/source/hooks/usePlaylist.d.ts +8 -0
- package/dist/source/hooks/usePlaylist.js +50 -0
- package/dist/source/hooks/useSearch.d.ts +8 -0
- package/dist/source/hooks/useSearch.js +76 -0
- package/dist/source/hooks/useTerminalSize.d.ts +4 -0
- package/dist/source/hooks/useTerminalSize.js +24 -0
- package/dist/source/hooks/useTheme.d.ts +6 -0
- package/dist/source/hooks/useTheme.js +5 -0
- package/dist/source/hooks/useYouTubeMusic.d.ts +11 -0
- package/dist/source/hooks/useYouTubeMusic.js +112 -0
- package/dist/source/main.d.ts +4 -0
- package/dist/source/main.js +69 -0
- package/dist/source/services/config/config.service.d.ts +26 -0
- package/dist/source/services/config/config.service.js +125 -0
- package/dist/source/services/logger/logger.service.d.ts +10 -0
- package/dist/source/services/logger/logger.service.js +52 -0
- package/dist/source/services/player/player.service.d.ts +58 -0
- package/dist/source/services/player/player.service.js +349 -0
- package/dist/source/services/player-state/player-state.service.d.ts +24 -0
- package/dist/source/services/player-state/player-state.service.js +122 -0
- package/dist/source/services/plugin/plugin-audio-api.d.ts +17 -0
- package/dist/source/services/plugin/plugin-audio-api.js +36 -0
- package/dist/source/services/plugin/plugin-context.d.ts +5 -0
- package/dist/source/services/plugin/plugin-context.js +256 -0
- package/dist/source/services/plugin/plugin-hooks.service.d.ts +62 -0
- package/dist/source/services/plugin/plugin-hooks.service.js +135 -0
- package/dist/source/services/plugin/plugin-installer.service.d.ts +27 -0
- package/dist/source/services/plugin/plugin-installer.service.js +247 -0
- package/dist/source/services/plugin/plugin-loader.service.d.ts +33 -0
- package/dist/source/services/plugin/plugin-loader.service.js +161 -0
- package/dist/source/services/plugin/plugin-permissions.service.d.ts +72 -0
- package/dist/source/services/plugin/plugin-permissions.service.js +194 -0
- package/dist/source/services/plugin/plugin-registry.service.d.ts +76 -0
- package/dist/source/services/plugin/plugin-registry.service.js +215 -0
- package/dist/source/services/plugin/plugin-ui-api.d.ts +25 -0
- package/dist/source/services/plugin/plugin-ui-api.js +46 -0
- package/dist/source/services/plugin/plugin-updater.service.d.ts +23 -0
- package/dist/source/services/plugin/plugin-updater.service.js +206 -0
- package/dist/source/services/youtube-music/api.d.ts +13 -0
- package/dist/source/services/youtube-music/api.js +371 -0
- package/dist/source/services/youtube-music/search.service.d.ts +11 -0
- package/dist/source/services/youtube-music/search.service.js +38 -0
- package/dist/source/stores/navigation.store.d.ts +10 -0
- package/dist/source/stores/navigation.store.js +67 -0
- package/dist/source/stores/player.store.d.ts +28 -0
- package/dist/source/stores/player.store.js +458 -0
- package/dist/source/stores/plugins.store.d.ts +46 -0
- package/dist/source/stores/plugins.store.js +177 -0
- package/dist/source/types/actions.d.ts +119 -0
- package/dist/source/types/actions.js +1 -0
- package/dist/source/types/cli.types.d.ts +14 -0
- package/dist/source/types/cli.types.js +1 -0
- package/dist/source/types/config.types.d.ts +19 -0
- package/dist/source/types/config.types.js +1 -0
- package/dist/source/types/keyboard.types.d.ts +5 -0
- package/dist/source/types/keyboard.types.js +1 -0
- package/dist/source/types/navigation.types.d.ts +14 -0
- package/dist/source/types/navigation.types.js +1 -0
- package/dist/source/types/player.types.d.ts +16 -0
- package/dist/source/types/player.types.js +1 -0
- package/dist/source/types/playlist.types.d.ts +12 -0
- package/dist/source/types/playlist.types.js +1 -0
- package/dist/source/types/plugin.types.d.ts +239 -0
- package/dist/source/types/plugin.types.js +1 -0
- package/dist/source/types/theme.types.d.ts +18 -0
- package/dist/source/types/theme.types.js +1 -0
- package/dist/source/types/youtube-music.types.d.ts +35 -0
- package/dist/source/types/youtube-music.types.js +1 -0
- package/dist/source/types/youtubei.types.d.ts +60 -0
- package/dist/source/types/youtubei.types.js +3 -0
- package/dist/source/utils/constants.d.ts +65 -0
- package/dist/source/utils/constants.js +82 -0
- package/dist/source/utils/format.d.ts +3 -0
- package/dist/source/utils/format.js +24 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +13 -0
- 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 {};
|