@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,247 @@
1
+ import { CONFIG_DIR } from "../../utils/constants.js";
2
+ import { logger } from "../logger/logger.service.js";
3
+ import { join } from 'node:path';
4
+ import { existsSync, mkdirSync, rmSync, cpSync } from 'node:fs';
5
+ import { execSync } from 'node:child_process';
6
+ const PLUGINS_DIR = join(CONFIG_DIR, 'plugins');
7
+ const DEFAULT_PLUGIN_REPO = 'https://github.com/involvex/youtube-music-cli-plugins';
8
+ /**
9
+ * Plugin installer service
10
+ */
11
+ class PluginInstallerService {
12
+ /**
13
+ * Install a plugin from GitHub repository
14
+ */
15
+ async installFromGitHub(repoUrl, pluginName) {
16
+ try {
17
+ logger.info('PluginInstallerService', `Installing from ${repoUrl}`);
18
+ // Ensure plugins directory exists
19
+ if (!existsSync(PLUGINS_DIR)) {
20
+ mkdirSync(PLUGINS_DIR, { recursive: true });
21
+ }
22
+ // Determine plugin name from URL if not provided
23
+ if (!pluginName) {
24
+ const match = repoUrl.match(/\/([^/]+?)(\.git)?$/);
25
+ pluginName = match?.[1] || 'unknown-plugin';
26
+ }
27
+ const targetDir = join(PLUGINS_DIR, pluginName);
28
+ // Check if plugin already exists
29
+ if (existsSync(targetDir)) {
30
+ return {
31
+ success: false,
32
+ error: `Plugin ${pluginName} is already installed`,
33
+ };
34
+ }
35
+ // Clone repository
36
+ try {
37
+ execSync(`git clone "${repoUrl}" "${targetDir}"`, {
38
+ stdio: 'pipe',
39
+ windowsHide: true,
40
+ });
41
+ }
42
+ catch (error) {
43
+ logger.error('PluginInstallerService', 'Git clone failed:', error);
44
+ return {
45
+ success: false,
46
+ error: `Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`,
47
+ };
48
+ }
49
+ // Validate plugin structure
50
+ const manifestPath = join(targetDir, 'plugin.json');
51
+ if (!existsSync(manifestPath)) {
52
+ // Cleanup
53
+ rmSync(targetDir, { recursive: true, force: true });
54
+ return {
55
+ success: false,
56
+ error: 'Invalid plugin: plugin.json not found',
57
+ };
58
+ }
59
+ // Check for npm dependencies
60
+ const packageJsonPath = join(targetDir, 'package.json');
61
+ if (existsSync(packageJsonPath)) {
62
+ logger.info('PluginInstallerService', 'Installing plugin dependencies...');
63
+ try {
64
+ execSync('bun install', {
65
+ cwd: targetDir,
66
+ stdio: 'pipe',
67
+ windowsHide: true,
68
+ });
69
+ }
70
+ catch (error) {
71
+ logger.error('PluginInstallerService', 'Failed to install dependencies:', error);
72
+ // Continue anyway - plugin might still work
73
+ }
74
+ }
75
+ logger.info('PluginInstallerService', `Successfully installed plugin: ${pluginName}`);
76
+ return {
77
+ success: true,
78
+ pluginId: pluginName,
79
+ message: `Plugin ${pluginName} installed successfully`,
80
+ };
81
+ }
82
+ catch (error) {
83
+ logger.error('PluginInstallerService', 'Install failed:', error);
84
+ return {
85
+ success: false,
86
+ error: `Installation failed: ${error instanceof Error ? error.message : String(error)}`,
87
+ };
88
+ }
89
+ }
90
+ /**
91
+ * Install a plugin from the default plugin repository
92
+ */
93
+ async installFromDefaultRepo(pluginName) {
94
+ logger.info('PluginInstallerService', `Installing ${pluginName} from default repo`);
95
+ // For now, use sparse checkout or clone entire repo and copy plugin
96
+ // Simplified: clone entire repo to temp, copy plugin, delete temp
97
+ try {
98
+ const tempDir = join(PLUGINS_DIR, '.temp-install');
99
+ // Clone default repo
100
+ if (!existsSync(tempDir)) {
101
+ execSync(`git clone "${DEFAULT_PLUGIN_REPO}" "${tempDir}"`, {
102
+ stdio: 'pipe',
103
+ windowsHide: true,
104
+ });
105
+ }
106
+ else {
107
+ // Pull latest
108
+ execSync('git pull', {
109
+ cwd: tempDir,
110
+ stdio: 'pipe',
111
+ windowsHide: true,
112
+ });
113
+ }
114
+ const pluginSourceDir = join(tempDir, 'plugins', pluginName);
115
+ if (!existsSync(pluginSourceDir)) {
116
+ rmSync(tempDir, { recursive: true, force: true });
117
+ return {
118
+ success: false,
119
+ error: `Plugin ${pluginName} not found in default repository`,
120
+ };
121
+ }
122
+ const targetDir = join(PLUGINS_DIR, pluginName);
123
+ if (existsSync(targetDir)) {
124
+ rmSync(tempDir, { recursive: true, force: true });
125
+ return {
126
+ success: false,
127
+ error: `Plugin ${pluginName} is already installed`,
128
+ };
129
+ }
130
+ // Copy plugin to plugins directory
131
+ cpSync(pluginSourceDir, targetDir, { recursive: true });
132
+ // Cleanup temp directory
133
+ rmSync(tempDir, { recursive: true, force: true });
134
+ // Install dependencies if needed
135
+ const packageJsonPath = join(targetDir, 'package.json');
136
+ if (existsSync(packageJsonPath)) {
137
+ try {
138
+ execSync('bun install', {
139
+ cwd: targetDir,
140
+ stdio: 'pipe',
141
+ windowsHide: true,
142
+ });
143
+ }
144
+ catch (error) {
145
+ logger.warn('PluginInstallerService', 'Failed to install dependencies:', error);
146
+ }
147
+ }
148
+ return {
149
+ success: true,
150
+ pluginId: pluginName,
151
+ message: `Plugin ${pluginName} installed successfully`,
152
+ };
153
+ }
154
+ catch (error) {
155
+ logger.error('PluginInstallerService', 'Install failed:', error);
156
+ return {
157
+ success: false,
158
+ error: `Installation failed: ${error instanceof Error ? error.message : String(error)}`,
159
+ };
160
+ }
161
+ }
162
+ /**
163
+ * Install a plugin from local directory (for development)
164
+ */
165
+ async installFromLocal(sourcePath) {
166
+ try {
167
+ if (!existsSync(sourcePath)) {
168
+ return {
169
+ success: false,
170
+ error: `Source path does not exist: ${sourcePath}`,
171
+ };
172
+ }
173
+ const manifestPath = join(sourcePath, 'plugin.json');
174
+ if (!existsSync(manifestPath)) {
175
+ return {
176
+ success: false,
177
+ error: 'Invalid plugin: plugin.json not found',
178
+ };
179
+ }
180
+ // Read manifest to get plugin ID
181
+ const { readFileSync: fsReadFileSync } = await import('node:fs');
182
+ const manifest = JSON.parse(fsReadFileSync(manifestPath, 'utf-8'));
183
+ const targetDir = join(PLUGINS_DIR, manifest.id);
184
+ if (existsSync(targetDir)) {
185
+ return {
186
+ success: false,
187
+ error: `Plugin ${manifest.id} is already installed`,
188
+ };
189
+ }
190
+ // Copy to plugins directory
191
+ if (!existsSync(PLUGINS_DIR)) {
192
+ mkdirSync(PLUGINS_DIR, { recursive: true });
193
+ }
194
+ cpSync(sourcePath, targetDir, { recursive: true });
195
+ return {
196
+ success: true,
197
+ pluginId: manifest.id,
198
+ message: `Plugin ${manifest.name} installed from local path`,
199
+ };
200
+ }
201
+ catch (error) {
202
+ logger.error('PluginInstallerService', 'Install failed:', error);
203
+ return {
204
+ success: false,
205
+ error: `Installation failed: ${error instanceof Error ? error.message : String(error)}`,
206
+ };
207
+ }
208
+ }
209
+ /**
210
+ * Uninstall a plugin
211
+ */
212
+ async uninstall(pluginId) {
213
+ const pluginDir = join(PLUGINS_DIR, pluginId);
214
+ if (!existsSync(pluginDir)) {
215
+ return {
216
+ success: false,
217
+ error: `Plugin ${pluginId} is not installed`,
218
+ };
219
+ }
220
+ try {
221
+ rmSync(pluginDir, { recursive: true, force: true });
222
+ return {
223
+ success: true,
224
+ pluginId,
225
+ message: `Plugin ${pluginId} uninstalled successfully`,
226
+ };
227
+ }
228
+ catch (error) {
229
+ logger.error('PluginInstallerService', 'Uninstall failed:', error);
230
+ return {
231
+ success: false,
232
+ error: `Uninstall failed: ${error instanceof Error ? error.message : String(error)}`,
233
+ };
234
+ }
235
+ }
236
+ }
237
+ // Singleton instance
238
+ let instance = null;
239
+ /**
240
+ * Get the plugin installer service singleton
241
+ */
242
+ export function getPluginInstallerService() {
243
+ if (!instance) {
244
+ instance = new PluginInstallerService();
245
+ }
246
+ return instance;
247
+ }
@@ -0,0 +1,33 @@
1
+ import type { Plugin, PluginInstance } from '../../types/plugin.types.ts';
2
+ /**
3
+ * Plugin loader service - handles dynamic loading of plugins
4
+ */
5
+ declare class PluginLoaderService {
6
+ private jiti;
7
+ constructor();
8
+ /**
9
+ * Load a plugin from a directory
10
+ */
11
+ loadPlugin(pluginPath: string): Promise<PluginInstance>;
12
+ /**
13
+ * Validate plugin module structure
14
+ */
15
+ private isValidPlugin;
16
+ /**
17
+ * Call plugin lifecycle hook safely
18
+ */
19
+ callHook(plugin: Plugin, hook: 'init' | 'enable' | 'disable' | 'destroy', context: PluginInstance['context']): Promise<void>;
20
+ /**
21
+ * Reload a plugin (useful for development)
22
+ */
23
+ reloadPlugin(pluginPath: string): Promise<PluginInstance>;
24
+ }
25
+ /**
26
+ * Get the plugin loader service singleton
27
+ */
28
+ export declare function getPluginLoaderService(): PluginLoaderService;
29
+ /**
30
+ * Reset the singleton (for testing)
31
+ */
32
+ export declare function resetPluginLoaderService(): void;
33
+ export {};
@@ -0,0 +1,161 @@
1
+ import { logger } from "../logger/logger.service.js";
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { createJiti } from 'jiti';
5
+ /**
6
+ * Validate plugin manifest
7
+ */
8
+ function validateManifest(manifest) {
9
+ if (typeof manifest !== 'object' || manifest === null) {
10
+ return false;
11
+ }
12
+ const m = manifest;
13
+ return (typeof m['id'] === 'string' &&
14
+ typeof m['name'] === 'string' &&
15
+ typeof m['version'] === 'string' &&
16
+ typeof m['description'] === 'string' &&
17
+ typeof m['author'] === 'string' &&
18
+ typeof m['main'] === 'string' &&
19
+ Array.isArray(m['permissions']));
20
+ }
21
+ /**
22
+ * Plugin loader service - handles dynamic loading of plugins
23
+ */
24
+ class PluginLoaderService {
25
+ jiti;
26
+ constructor() {
27
+ // Initialize jiti for dynamic TypeScript loading
28
+ this.jiti = createJiti(import.meta.url, {
29
+ interopDefault: true,
30
+ requireCache: false,
31
+ });
32
+ }
33
+ /**
34
+ * Load a plugin from a directory
35
+ */
36
+ async loadPlugin(pluginPath) {
37
+ logger.info('PluginLoaderService', `Loading plugin from ${pluginPath}`);
38
+ // Load and validate manifest
39
+ const manifestPath = join(pluginPath, 'plugin.json');
40
+ if (!existsSync(manifestPath)) {
41
+ throw new Error(`Plugin manifest not found: ${manifestPath}`);
42
+ }
43
+ const manifestData = readFileSync(manifestPath, 'utf-8');
44
+ const manifest = JSON.parse(manifestData);
45
+ if (!validateManifest(manifest)) {
46
+ throw new Error(`Invalid plugin manifest: ${manifestPath}`);
47
+ }
48
+ logger.debug('PluginLoaderService', `Validated manifest for ${manifest.name} v${manifest.version}`);
49
+ // Load plugin module
50
+ const pluginEntryPath = join(pluginPath, manifest.main);
51
+ if (!existsSync(pluginEntryPath)) {
52
+ throw new Error(`Plugin entry point not found: ${pluginEntryPath}`);
53
+ }
54
+ let pluginModule;
55
+ try {
56
+ // Use jiti to load TypeScript/JavaScript module
57
+ pluginModule = await this.jiti.import(pluginEntryPath);
58
+ }
59
+ catch (error) {
60
+ logger.error('PluginLoaderService', `Failed to load plugin module:`, error);
61
+ throw new Error(`Failed to load plugin from ${pluginEntryPath}: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+ // Validate plugin module
64
+ if (!this.isValidPlugin(pluginModule)) {
65
+ throw new Error(`Invalid plugin module: ${pluginEntryPath}`);
66
+ }
67
+ const plugin = pluginModule;
68
+ // Verify manifest consistency
69
+ if (plugin.manifest.id !== manifest.id) {
70
+ logger.warn('PluginLoaderService', `Plugin manifest ID mismatch: ${plugin.manifest.id} vs ${manifest.id}`);
71
+ }
72
+ logger.info('PluginLoaderService', `Successfully loaded plugin: ${manifest.name} v${manifest.version}`);
73
+ // Create plugin instance (context will be injected later)
74
+ const instance = {
75
+ manifest,
76
+ plugin,
77
+ // Note: Context is set by plugin registry
78
+ context: null,
79
+ config: {
80
+ enabled: false,
81
+ config: {},
82
+ permissions: {},
83
+ },
84
+ enabled: false,
85
+ loadedAt: Date.now(),
86
+ };
87
+ return instance;
88
+ }
89
+ /**
90
+ * Validate plugin module structure
91
+ */
92
+ isValidPlugin(module) {
93
+ if (typeof module !== 'object' || module === null) {
94
+ return false;
95
+ }
96
+ const m = module;
97
+ // Must have manifest
98
+ if (!m['manifest'] || typeof m['manifest'] !== 'object') {
99
+ return false;
100
+ }
101
+ // Optional lifecycle hooks must be functions if present
102
+ if (m['init'] && typeof m['init'] !== 'function') {
103
+ return false;
104
+ }
105
+ if (m['enable'] && typeof m['enable'] !== 'function') {
106
+ return false;
107
+ }
108
+ if (m['disable'] && typeof m['disable'] !== 'function') {
109
+ return false;
110
+ }
111
+ if (m['destroy'] && typeof m['destroy'] !== 'function') {
112
+ return false;
113
+ }
114
+ return true;
115
+ }
116
+ /**
117
+ * Call plugin lifecycle hook safely
118
+ */
119
+ async callHook(plugin, hook, context) {
120
+ const hookFn = plugin[hook];
121
+ if (!hookFn) {
122
+ logger.debug('PluginLoaderService', `Plugin ${plugin.manifest.name} has no ${hook} hook`);
123
+ return;
124
+ }
125
+ try {
126
+ logger.debug('PluginLoaderService', `Calling ${hook} hook for ${plugin.manifest.name}`);
127
+ await Promise.resolve(hookFn(context));
128
+ logger.debug('PluginLoaderService', `${hook} hook completed for ${plugin.manifest.name}`);
129
+ }
130
+ catch (error) {
131
+ logger.error('PluginLoaderService', `Error in ${hook} hook for ${plugin.manifest.name}:`, error);
132
+ throw error;
133
+ }
134
+ }
135
+ /**
136
+ * Reload a plugin (useful for development)
137
+ */
138
+ async reloadPlugin(pluginPath) {
139
+ logger.info('PluginLoaderService', `Reloading plugin from ${pluginPath}`);
140
+ // Clear jiti cache for this plugin
141
+ // Note: jiti has requireCache: false, so this should work automatically
142
+ return this.loadPlugin(pluginPath);
143
+ }
144
+ }
145
+ // Singleton instance
146
+ let instance = null;
147
+ /**
148
+ * Get the plugin loader service singleton
149
+ */
150
+ export function getPluginLoaderService() {
151
+ if (!instance) {
152
+ instance = new PluginLoaderService();
153
+ }
154
+ return instance;
155
+ }
156
+ /**
157
+ * Reset the singleton (for testing)
158
+ */
159
+ export function resetPluginLoaderService() {
160
+ instance = null;
161
+ }
@@ -0,0 +1,72 @@
1
+ import type { PluginPermission, PluginPermissions, PermissionStatus } from '../../types/plugin.types.ts';
2
+ /**
3
+ * Plugin permissions service - manages permission grants and denials
4
+ */
5
+ declare class PluginPermissionsService {
6
+ private permissions;
7
+ private permissionsPath;
8
+ private configDir;
9
+ onPermissionRequest?: (pluginId: string, permission: PluginPermission) => Promise<boolean>;
10
+ constructor();
11
+ /**
12
+ * Load permissions from disk
13
+ */
14
+ private load;
15
+ /**
16
+ * Save permissions to disk
17
+ */
18
+ private save;
19
+ /**
20
+ * Check if a plugin has a specific permission
21
+ */
22
+ hasPermission(pluginId: string, permission: PluginPermission): boolean;
23
+ /**
24
+ * Get permission status
25
+ */
26
+ getPermissionStatus(pluginId: string, permission: PluginPermission): PermissionStatus;
27
+ /**
28
+ * Get all permissions for a plugin
29
+ */
30
+ getPermissions(pluginId: string): PluginPermissions;
31
+ /**
32
+ * Grant a permission to a plugin
33
+ */
34
+ grantPermission(pluginId: string, permission: PluginPermission): void;
35
+ /**
36
+ * Deny a permission to a plugin
37
+ */
38
+ denyPermission(pluginId: string, permission: PluginPermission): void;
39
+ /**
40
+ * Request permission from user
41
+ */
42
+ requestPermission(pluginId: string, permission: PluginPermission): Promise<boolean>;
43
+ /**
44
+ * Grant multiple permissions at once
45
+ */
46
+ grantPermissions(pluginId: string, permissions: PluginPermission[]): void;
47
+ /**
48
+ * Revoke a permission
49
+ */
50
+ revokePermission(pluginId: string, permission: PluginPermission): void;
51
+ /**
52
+ * Revoke all permissions for a plugin
53
+ */
54
+ revokeAllPermissions(pluginId: string): void;
55
+ /**
56
+ * Get all plugin IDs with permissions
57
+ */
58
+ getAllPluginIds(): string[];
59
+ /**
60
+ * Reset all permissions (for testing or user request)
61
+ */
62
+ resetAll(): void;
63
+ }
64
+ /**
65
+ * Get the plugin permissions service singleton
66
+ */
67
+ export declare function getPluginPermissionsService(): PluginPermissionsService;
68
+ /**
69
+ * Reset the singleton (for testing)
70
+ */
71
+ export declare function resetPluginPermissionsService(): void;
72
+ export {};