@involvex/youtube-music-cli 0.0.47 → 0.0.48

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 (111) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cli.js.map +3 -3
  3. package/dist/youtube-music-cli +0 -0
  4. package/package.json +1 -1
  5. package/dist/eslint.config.js +0 -55
  6. package/dist/package.json +0 -120
  7. package/dist/scripts/build-cli.js +0 -46
  8. package/dist/source/app.js +0 -17
  9. package/dist/source/cli.js +0 -504
  10. package/dist/source/components/common/ErrorBoundary.js +0 -22
  11. package/dist/source/components/common/Help.js +0 -18
  12. package/dist/source/components/common/ShortcutsBar.js +0 -89
  13. package/dist/source/components/config/ConfigLayout.js +0 -84
  14. package/dist/source/components/config/KeybindingsLayout.js +0 -107
  15. package/dist/source/components/export/ExportLayout.js +0 -111
  16. package/dist/source/components/import/ImportLayout.js +0 -119
  17. package/dist/source/components/import/ImportProgress.js +0 -73
  18. package/dist/source/components/layouts/ExploreLayout.js +0 -72
  19. package/dist/source/components/layouts/HistoryLayout.js +0 -37
  20. package/dist/source/components/layouts/LyricsLayout.js +0 -89
  21. package/dist/source/components/layouts/MainLayout.js +0 -190
  22. package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
  23. package/dist/source/components/layouts/PlayerLayout.js +0 -9
  24. package/dist/source/components/layouts/PluginsLayout.js +0 -77
  25. package/dist/source/components/layouts/SearchLayout.js +0 -193
  26. package/dist/source/components/layouts/TrendingLayout.js +0 -59
  27. package/dist/source/components/player/NowPlaying.js +0 -45
  28. package/dist/source/components/player/PlayerControls.js +0 -83
  29. package/dist/source/components/player/ProgressBar.js +0 -19
  30. package/dist/source/components/player/QueueList.js +0 -36
  31. package/dist/source/components/player/Suggestions.js +0 -50
  32. package/dist/source/components/playlist/PlaylistList.js +0 -138
  33. package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
  34. package/dist/source/components/plugins/PluginsAvailable.js +0 -55
  35. package/dist/source/components/plugins/PluginsList.js +0 -18
  36. package/dist/source/components/search/SearchBar.js +0 -55
  37. package/dist/source/components/search/SearchHistory.js +0 -35
  38. package/dist/source/components/search/SearchResults.js +0 -280
  39. package/dist/source/components/settings/Settings.js +0 -211
  40. package/dist/source/components/theme/ThemeSwitcher.js +0 -11
  41. package/dist/source/config/themes.config.js +0 -123
  42. package/dist/source/contexts/theme.context.js +0 -29
  43. package/dist/source/hooks/useKeyboard.js +0 -188
  44. package/dist/source/hooks/useKeyboardBlocker.js +0 -45
  45. package/dist/source/hooks/useNavigation.js +0 -5
  46. package/dist/source/hooks/usePlayer.js +0 -43
  47. package/dist/source/hooks/usePlaylist.js +0 -65
  48. package/dist/source/hooks/useSearch.js +0 -76
  49. package/dist/source/hooks/useSleepTimer.js +0 -48
  50. package/dist/source/hooks/useTerminalSize.js +0 -24
  51. package/dist/source/hooks/useTheme.js +0 -5
  52. package/dist/source/hooks/useYouTubeMusic.js +0 -112
  53. package/dist/source/main.js +0 -127
  54. package/dist/source/services/cache/cache.service.js +0 -67
  55. package/dist/source/services/completions/completions.service.js +0 -313
  56. package/dist/source/services/config/config.service.js +0 -191
  57. package/dist/source/services/discord/discord-rpc.service.js +0 -95
  58. package/dist/source/services/download/download.service.js +0 -350
  59. package/dist/source/services/export/export.service.js +0 -131
  60. package/dist/source/services/history/history.service.js +0 -83
  61. package/dist/source/services/import/import.service.js +0 -272
  62. package/dist/source/services/import/spotify.service.js +0 -171
  63. package/dist/source/services/import/track-matcher.service.js +0 -271
  64. package/dist/source/services/import/youtube-import.service.js +0 -84
  65. package/dist/source/services/logger/logger.service.js +0 -52
  66. package/dist/source/services/lyrics/lyrics.service.js +0 -93
  67. package/dist/source/services/mpris/mpris.service.js +0 -78
  68. package/dist/source/services/notification/notification.service.js +0 -57
  69. package/dist/source/services/player/dependency-check.service.js +0 -140
  70. package/dist/source/services/player/player.service.js +0 -478
  71. package/dist/source/services/player-state/player-state.service.js +0 -123
  72. package/dist/source/services/plugin/plugin-audio-api.js +0 -36
  73. package/dist/source/services/plugin/plugin-context.js +0 -256
  74. package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
  75. package/dist/source/services/plugin/plugin-installer.service.js +0 -248
  76. package/dist/source/services/plugin/plugin-loader.service.js +0 -161
  77. package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
  78. package/dist/source/services/plugin/plugin-registry.service.js +0 -215
  79. package/dist/source/services/plugin/plugin-ui-api.js +0 -46
  80. package/dist/source/services/plugin/plugin-updater.service.js +0 -206
  81. package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
  82. package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
  83. package/dist/source/services/version-check/version-check.service.js +0 -121
  84. package/dist/source/services/web/static-file.service.js +0 -185
  85. package/dist/source/services/web/web-server-manager.js +0 -507
  86. package/dist/source/services/web/web-streaming.service.js +0 -292
  87. package/dist/source/services/web/websocket.server.js +0 -267
  88. package/dist/source/services/youtube-music/api.js +0 -649
  89. package/dist/source/services/youtube-music/search.service.js +0 -38
  90. package/dist/source/stores/history.store.js +0 -64
  91. package/dist/source/stores/navigation.store.js +0 -90
  92. package/dist/source/stores/player.store.js +0 -789
  93. package/dist/source/stores/plugins.store.js +0 -177
  94. package/dist/source/types/actions.js +0 -1
  95. package/dist/source/types/cli.types.js +0 -1
  96. package/dist/source/types/config.types.js +0 -1
  97. package/dist/source/types/history.types.js +0 -1
  98. package/dist/source/types/import.types.js +0 -2
  99. package/dist/source/types/keyboard.types.js +0 -1
  100. package/dist/source/types/navigation.types.js +0 -1
  101. package/dist/source/types/player.types.js +0 -1
  102. package/dist/source/types/playlist.types.js +0 -1
  103. package/dist/source/types/plugin.types.js +0 -1
  104. package/dist/source/types/theme.types.js +0 -1
  105. package/dist/source/types/web.types.js +0 -2
  106. package/dist/source/types/youtube-music.types.js +0 -1
  107. package/dist/source/types/youtubei.types.js +0 -3
  108. package/dist/source/utils/constants.js +0 -135
  109. package/dist/source/utils/format.js +0 -24
  110. package/dist/source/utils/icons.js +0 -28
  111. package/dist/source/utils/search-filters.js +0 -100
@@ -1,248 +0,0 @@
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
- // Plugin is at root of repo (e.g., adblock/, lyrics/, etc.)
115
- const pluginSourceDir = join(tempDir, pluginName);
116
- if (!existsSync(pluginSourceDir)) {
117
- rmSync(tempDir, { recursive: true, force: true });
118
- return {
119
- success: false,
120
- error: `Plugin ${pluginName} not found in default repository`,
121
- };
122
- }
123
- const targetDir = join(PLUGINS_DIR, pluginName);
124
- if (existsSync(targetDir)) {
125
- rmSync(tempDir, { recursive: true, force: true });
126
- return {
127
- success: false,
128
- error: `Plugin ${pluginName} is already installed`,
129
- };
130
- }
131
- // Copy plugin to plugins directory
132
- cpSync(pluginSourceDir, targetDir, { recursive: true });
133
- // Cleanup temp directory
134
- rmSync(tempDir, { recursive: true, force: true });
135
- // Install dependencies if needed
136
- const packageJsonPath = join(targetDir, 'package.json');
137
- if (existsSync(packageJsonPath)) {
138
- try {
139
- execSync('bun install', {
140
- cwd: targetDir,
141
- stdio: 'pipe',
142
- windowsHide: true,
143
- });
144
- }
145
- catch (error) {
146
- logger.warn('PluginInstallerService', 'Failed to install dependencies:', error);
147
- }
148
- }
149
- return {
150
- success: true,
151
- pluginId: pluginName,
152
- message: `Plugin ${pluginName} installed successfully`,
153
- };
154
- }
155
- catch (error) {
156
- logger.error('PluginInstallerService', 'Install failed:', error);
157
- return {
158
- success: false,
159
- error: `Installation failed: ${error instanceof Error ? error.message : String(error)}`,
160
- };
161
- }
162
- }
163
- /**
164
- * Install a plugin from local directory (for development)
165
- */
166
- async installFromLocal(sourcePath) {
167
- try {
168
- if (!existsSync(sourcePath)) {
169
- return {
170
- success: false,
171
- error: `Source path does not exist: ${sourcePath}`,
172
- };
173
- }
174
- const manifestPath = join(sourcePath, 'plugin.json');
175
- if (!existsSync(manifestPath)) {
176
- return {
177
- success: false,
178
- error: 'Invalid plugin: plugin.json not found',
179
- };
180
- }
181
- // Read manifest to get plugin ID
182
- const { readFileSync: fsReadFileSync } = await import('node:fs');
183
- const manifest = JSON.parse(fsReadFileSync(manifestPath, 'utf-8'));
184
- const targetDir = join(PLUGINS_DIR, manifest.id);
185
- if (existsSync(targetDir)) {
186
- return {
187
- success: false,
188
- error: `Plugin ${manifest.id} is already installed`,
189
- };
190
- }
191
- // Copy to plugins directory
192
- if (!existsSync(PLUGINS_DIR)) {
193
- mkdirSync(PLUGINS_DIR, { recursive: true });
194
- }
195
- cpSync(sourcePath, targetDir, { recursive: true });
196
- return {
197
- success: true,
198
- pluginId: manifest.id,
199
- message: `Plugin ${manifest.name} installed from local path`,
200
- };
201
- }
202
- catch (error) {
203
- logger.error('PluginInstallerService', 'Install failed:', error);
204
- return {
205
- success: false,
206
- error: `Installation failed: ${error instanceof Error ? error.message : String(error)}`,
207
- };
208
- }
209
- }
210
- /**
211
- * Uninstall a plugin
212
- */
213
- async uninstall(pluginId) {
214
- const pluginDir = join(PLUGINS_DIR, pluginId);
215
- if (!existsSync(pluginDir)) {
216
- return {
217
- success: false,
218
- error: `Plugin ${pluginId} is not installed`,
219
- };
220
- }
221
- try {
222
- rmSync(pluginDir, { recursive: true, force: true });
223
- return {
224
- success: true,
225
- pluginId,
226
- message: `Plugin ${pluginId} uninstalled successfully`,
227
- };
228
- }
229
- catch (error) {
230
- logger.error('PluginInstallerService', 'Uninstall failed:', error);
231
- return {
232
- success: false,
233
- error: `Uninstall failed: ${error instanceof Error ? error.message : String(error)}`,
234
- };
235
- }
236
- }
237
- }
238
- // Singleton instance
239
- let instance = null;
240
- /**
241
- * Get the plugin installer service singleton
242
- */
243
- export function getPluginInstallerService() {
244
- if (!instance) {
245
- instance = new PluginInstallerService();
246
- }
247
- return instance;
248
- }
@@ -1,161 +0,0 @@
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
- }
@@ -1,194 +0,0 @@
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
- }