claude-code-templates 1.21.12 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,806 @@
1
+ // Plugin Dashboard Application - Enhanced UX
2
+ class PluginDashboard {
3
+ constructor() {
4
+ this.data = {
5
+ marketplaces: [],
6
+ plugins: [],
7
+ summary: {}
8
+ };
9
+
10
+ this.state = {
11
+ selectedMarketplace: null,
12
+ pluginStatusFilter: 'all',
13
+ marketplaceStatusFilter: 'all',
14
+ searchQuery: '',
15
+ sortBy: 'status',
16
+ viewMode: 'grid',
17
+ sidebarCollapsed: false
18
+ };
19
+ }
20
+
21
+ async init() {
22
+ this.setupEventListeners();
23
+ await this.loadData();
24
+ this.restoreViewPreferences();
25
+ this.startAutoRefresh();
26
+ }
27
+
28
+ setupEventListeners() {
29
+ // Sidebar toggle
30
+ const sidebarToggle = document.getElementById('sidebarToggle');
31
+ if (sidebarToggle) {
32
+ sidebarToggle.addEventListener('click', () => this.toggleSidebar());
33
+ }
34
+
35
+ // Refresh button
36
+ const refreshBtn = document.getElementById('refreshBtn');
37
+ if (refreshBtn) {
38
+ refreshBtn.addEventListener('click', () => this.refreshData());
39
+ }
40
+
41
+ // Marketplace filter buttons in sidebar
42
+ document.querySelectorAll('.marketplace-filter-btn').forEach(btn => {
43
+ btn.addEventListener('click', (e) => {
44
+ const filter = e.target.dataset.filter;
45
+ this.setMarketplaceStatusFilter(filter);
46
+ });
47
+ });
48
+
49
+ // Plugin status filter chips
50
+ document.querySelectorAll('.filter-chip').forEach(chip => {
51
+ chip.addEventListener('click', (e) => {
52
+ const filter = e.currentTarget.dataset.filter;
53
+ this.setPluginStatusFilter(filter);
54
+ });
55
+ });
56
+
57
+ // Search input
58
+ const searchInput = document.getElementById('pluginSearch');
59
+ if (searchInput) {
60
+ searchInput.addEventListener('input', (e) => {
61
+ this.state.searchQuery = e.target.value.toLowerCase().trim();
62
+ this.renderPlugins();
63
+ });
64
+ }
65
+
66
+ // Sort select
67
+ const sortSelect = document.getElementById('sortSelect');
68
+ if (sortSelect) {
69
+ sortSelect.addEventListener('change', (e) => {
70
+ this.state.sortBy = e.target.value;
71
+ this.renderPlugins();
72
+ });
73
+ }
74
+
75
+ // View mode toggle
76
+ document.querySelectorAll('.view-btn').forEach(btn => {
77
+ btn.addEventListener('click', (e) => {
78
+ const view = e.currentTarget.dataset.view;
79
+ this.setViewMode(view);
80
+ });
81
+ });
82
+
83
+ // Clear filters button
84
+ const clearFiltersBtn = document.getElementById('clearFiltersBtn');
85
+ if (clearFiltersBtn) {
86
+ clearFiltersBtn.addEventListener('click', () => this.clearAllFilters());
87
+ }
88
+
89
+ // Modal close handlers
90
+ const modalCloseBtn = document.getElementById('modalCloseBtn');
91
+ const modalOverlay = document.getElementById('pluginModal');
92
+
93
+ if (modalCloseBtn) {
94
+ modalCloseBtn.addEventListener('click', () => this.closeModal());
95
+ }
96
+
97
+ if (modalOverlay) {
98
+ modalOverlay.addEventListener('click', (e) => {
99
+ if (e.target === modalOverlay) {
100
+ this.closeModal();
101
+ }
102
+ });
103
+ }
104
+
105
+ // Keyboard shortcuts
106
+ document.addEventListener('keydown', (e) => {
107
+ if (e.key === 'Escape') {
108
+ this.closeModal();
109
+ }
110
+ if (e.key === '/' && e.target.tagName !== 'INPUT') {
111
+ e.preventDefault();
112
+ searchInput?.focus();
113
+ }
114
+ });
115
+ }
116
+
117
+ async loadData() {
118
+ try {
119
+ const [marketplacesRes, pluginsRes, summaryRes] = await Promise.all([
120
+ fetch('/api/marketplaces'),
121
+ fetch('/api/plugins'),
122
+ fetch('/api/summary')
123
+ ]);
124
+
125
+ const [marketplacesData, pluginsData, summaryData] = await Promise.all([
126
+ marketplacesRes.json(),
127
+ pluginsRes.json(),
128
+ summaryRes.json()
129
+ ]);
130
+
131
+ this.data.marketplaces = marketplacesData.marketplaces || [];
132
+ this.data.plugins = pluginsData.plugins || [];
133
+ this.data.summary = summaryData;
134
+
135
+ this.renderAll();
136
+ } catch (error) {
137
+ console.error('Error loading data:', error);
138
+ this.showError('Failed to load dashboard data');
139
+ }
140
+ }
141
+
142
+ renderAll() {
143
+ this.updateSidebarStats();
144
+ this.renderMarketplaces();
145
+ this.renderPlugins();
146
+ this.updateFilterCounts();
147
+ }
148
+
149
+ updateSidebarStats() {
150
+ const totalPlugins = this.data.plugins.length;
151
+ const enabledPlugins = this.data.plugins.filter(p => p.enabled).length;
152
+
153
+ document.getElementById('sidebarTotalPlugins').textContent = totalPlugins;
154
+ document.getElementById('sidebarEnabledPlugins').textContent = enabledPlugins;
155
+ }
156
+
157
+ getFilteredMarketplaces() {
158
+ let filtered = [...this.data.marketplaces];
159
+
160
+ if (this.state.marketplaceStatusFilter === 'enabled') {
161
+ filtered = filtered.filter(m => m.enabled);
162
+ } else if (this.state.marketplaceStatusFilter === 'disabled') {
163
+ filtered = filtered.filter(m => !m.enabled);
164
+ }
165
+
166
+ return filtered;
167
+ }
168
+
169
+ renderMarketplaces() {
170
+ const nav = document.getElementById('marketplaceNav');
171
+ if (!nav) return;
172
+
173
+ const marketplaces = this.getFilteredMarketplaces();
174
+
175
+ document.getElementById('marketplaceCount').textContent = marketplaces.length;
176
+
177
+ if (marketplaces.length === 0) {
178
+ nav.innerHTML = `
179
+ <div class="sidebar-empty">
180
+ <p>No marketplaces found</p>
181
+ </div>
182
+ `;
183
+ return;
184
+ }
185
+
186
+ // Add "All Plugins" option
187
+ const allPluginsActive = this.state.selectedMarketplace === null ? 'active' : '';
188
+ let html = `
189
+ <button class="marketplace-item ${allPluginsActive}" data-marketplace="all">
190
+ <span class="marketplace-icon">🧩</span>
191
+ <div class="marketplace-info">
192
+ <span class="marketplace-name">All Plugins</span>
193
+ <span class="marketplace-count">${this.data.plugins.length} plugins</span>
194
+ </div>
195
+ </button>
196
+ `;
197
+
198
+ // Add individual marketplaces
199
+ html += marketplaces.map(marketplace => {
200
+ const isActive = this.state.selectedMarketplace === marketplace.name ? 'active' : '';
201
+ const statusClass = marketplace.enabled ? 'enabled' : 'disabled';
202
+ const icon = this.getMarketplaceIcon(marketplace.type);
203
+
204
+ return `
205
+ <button class="marketplace-item ${isActive} ${statusClass}" data-marketplace="${this.escapeHtml(marketplace.name)}">
206
+ <span class="marketplace-icon">${icon}</span>
207
+ <div class="marketplace-info">
208
+ <span class="marketplace-name">${this.escapeHtml(marketplace.name)}</span>
209
+ <span class="marketplace-count">${marketplace.pluginCount} plugins</span>
210
+ </div>
211
+ ${marketplace.enabled
212
+ ? '<span class="marketplace-status enabled"></span>'
213
+ : '<span class="marketplace-status disabled"></span>'}
214
+ </button>
215
+ `;
216
+ }).join('');
217
+
218
+ nav.innerHTML = html;
219
+
220
+ // Add click handlers
221
+ nav.querySelectorAll('.marketplace-item').forEach(item => {
222
+ item.addEventListener('click', (e) => {
223
+ const marketplace = e.currentTarget.dataset.marketplace;
224
+ this.selectMarketplace(marketplace === 'all' ? null : marketplace);
225
+ });
226
+ });
227
+ }
228
+
229
+ selectMarketplace(marketplaceName) {
230
+ this.state.selectedMarketplace = marketplaceName;
231
+ this.renderMarketplaces();
232
+ this.renderPlugins();
233
+ this.updatePageTitle();
234
+ }
235
+
236
+ updatePageTitle() {
237
+ const nameEl = document.getElementById('currentMarketplaceName');
238
+ const iconEl = document.getElementById('currentMarketplaceIcon');
239
+
240
+ if (this.state.selectedMarketplace) {
241
+ const marketplace = this.data.marketplaces.find(m => m.name === this.state.selectedMarketplace);
242
+ nameEl.textContent = this.state.selectedMarketplace;
243
+ iconEl.textContent = marketplace ? this.getMarketplaceIcon(marketplace.type) : '📦';
244
+ } else {
245
+ nameEl.textContent = 'All Plugins';
246
+ iconEl.textContent = '🧩';
247
+ }
248
+ }
249
+
250
+ getMarketplaceIcon(type) {
251
+ const icons = {
252
+ 'GitHub': '🐙',
253
+ 'Git': '📁',
254
+ 'Local': '💻',
255
+ 'default': '📦'
256
+ };
257
+ return icons[type] || icons.default;
258
+ }
259
+
260
+ setMarketplaceStatusFilter(filter) {
261
+ this.state.marketplaceStatusFilter = filter;
262
+
263
+ document.querySelectorAll('.marketplace-filter-btn').forEach(btn => {
264
+ btn.classList.toggle('active', btn.dataset.filter === filter);
265
+ });
266
+
267
+ this.renderMarketplaces();
268
+ }
269
+
270
+ setPluginStatusFilter(filter) {
271
+ this.state.pluginStatusFilter = filter;
272
+
273
+ document.querySelectorAll('.filter-chip').forEach(chip => {
274
+ chip.classList.toggle('active', chip.dataset.filter === filter);
275
+ });
276
+
277
+ this.renderPlugins();
278
+ }
279
+
280
+ getFilteredPlugins() {
281
+ let filtered = [...this.data.plugins];
282
+
283
+ // Filter by marketplace
284
+ if (this.state.selectedMarketplace) {
285
+ filtered = filtered.filter(p => p.marketplace === this.state.selectedMarketplace);
286
+ }
287
+
288
+ // Filter by status
289
+ if (this.state.pluginStatusFilter === 'enabled') {
290
+ filtered = filtered.filter(p => p.enabled);
291
+ } else if (this.state.pluginStatusFilter === 'disabled') {
292
+ filtered = filtered.filter(p => !p.enabled);
293
+ }
294
+
295
+ // Filter by search query
296
+ if (this.state.searchQuery) {
297
+ filtered = filtered.filter(p => {
298
+ const searchStr = this.state.searchQuery;
299
+ return (
300
+ p.name.toLowerCase().includes(searchStr) ||
301
+ p.description.toLowerCase().includes(searchStr) ||
302
+ p.marketplace.toLowerCase().includes(searchStr) ||
303
+ (p.category && p.category.toLowerCase().includes(searchStr))
304
+ );
305
+ });
306
+ }
307
+
308
+ // Sort plugins
309
+ filtered = this.sortPlugins(filtered);
310
+
311
+ return filtered;
312
+ }
313
+
314
+ sortPlugins(plugins) {
315
+ const sorted = [...plugins];
316
+
317
+ switch (this.state.sortBy) {
318
+ case 'name':
319
+ sorted.sort((a, b) => a.name.localeCompare(b.name));
320
+ break;
321
+ case 'components':
322
+ sorted.sort((a, b) => {
323
+ const aTotal = Object.values(a.components).reduce((sum, val) => sum + val, 0);
324
+ const bTotal = Object.values(b.components).reduce((sum, val) => sum + val, 0);
325
+ return bTotal - aTotal;
326
+ });
327
+ break;
328
+ case 'status':
329
+ sorted.sort((a, b) => {
330
+ if (a.enabled === b.enabled) return a.name.localeCompare(b.name);
331
+ return a.enabled ? -1 : 1;
332
+ });
333
+ break;
334
+ }
335
+
336
+ return sorted;
337
+ }
338
+
339
+ updateFilterCounts() {
340
+ const allPlugins = this.getFilteredPluginsBase();
341
+ const enabled = allPlugins.filter(p => p.enabled).length;
342
+ const disabled = allPlugins.filter(p => !p.enabled).length;
343
+
344
+ document.getElementById('countAll').textContent = allPlugins.length;
345
+ document.getElementById('countEnabled').textContent = enabled;
346
+ document.getElementById('countDisabled').textContent = disabled;
347
+ }
348
+
349
+ getFilteredPluginsBase() {
350
+ let filtered = [...this.data.plugins];
351
+
352
+ if (this.state.selectedMarketplace) {
353
+ filtered = filtered.filter(p => p.marketplace === this.state.selectedMarketplace);
354
+ }
355
+
356
+ if (this.state.searchQuery) {
357
+ filtered = filtered.filter(p => {
358
+ const searchStr = this.state.searchQuery;
359
+ return (
360
+ p.name.toLowerCase().includes(searchStr) ||
361
+ p.description.toLowerCase().includes(searchStr) ||
362
+ p.marketplace.toLowerCase().includes(searchStr) ||
363
+ (p.category && p.category.toLowerCase().includes(searchStr))
364
+ );
365
+ });
366
+ }
367
+
368
+ return filtered;
369
+ }
370
+
371
+ renderPlugins() {
372
+ const container = document.getElementById('pluginsContainer');
373
+ const emptyState = document.getElementById('emptyState');
374
+ const clearFiltersBtn = document.getElementById('clearFiltersBtn');
375
+
376
+ if (!container) return;
377
+
378
+ const plugins = this.getFilteredPlugins();
379
+ this.updateFilterCounts();
380
+
381
+ if (plugins.length === 0) {
382
+ container.innerHTML = '';
383
+ emptyState.style.display = 'flex';
384
+
385
+ const hasFilters = this.state.selectedMarketplace ||
386
+ this.state.pluginStatusFilter !== 'all' ||
387
+ this.state.searchQuery;
388
+
389
+ clearFiltersBtn.style.display = hasFilters ? 'inline-block' : 'none';
390
+
391
+ document.getElementById('emptyDescription').textContent = hasFilters
392
+ ? 'Try adjusting your filters or search terms'
393
+ : 'No plugins available in this marketplace';
394
+
395
+ return;
396
+ }
397
+
398
+ emptyState.style.display = 'none';
399
+
400
+ const viewClass = this.state.viewMode === 'list' ? 'plugins-list' : 'plugins-grid';
401
+ container.className = `plugins-container ${viewClass}`;
402
+
403
+ if (this.state.viewMode === 'grid') {
404
+ container.innerHTML = plugins.map(plugin => this.renderPluginCard(plugin)).join('');
405
+ } else {
406
+ container.innerHTML = plugins.map(plugin => this.renderPluginListItem(plugin)).join('');
407
+ }
408
+
409
+ // Add click handlers to cards
410
+ container.querySelectorAll('[data-plugin]').forEach(card => {
411
+ card.addEventListener('click', () => {
412
+ const pluginName = card.dataset.plugin;
413
+ this.showPluginDetails(pluginName);
414
+ });
415
+ });
416
+ }
417
+
418
+ renderPluginCard(plugin) {
419
+ const statusClass = plugin.enabled ? 'enabled' : 'disabled';
420
+ const totalComponents = Object.values(plugin.components).reduce((sum, val) => sum + val, 0);
421
+
422
+ return `
423
+ <article class="plugin-card ${statusClass}" data-plugin="${this.escapeHtml(plugin.name)}">
424
+ <div class="plugin-card-header">
425
+ <div class="plugin-card-status ${statusClass}"></div>
426
+ <h3 class="plugin-card-title">${this.escapeHtml(plugin.name)}</h3>
427
+ <span class="plugin-card-version">v${plugin.version}</span>
428
+ </div>
429
+
430
+ <p class="plugin-card-description">${this.escapeHtml(plugin.description)}</p>
431
+
432
+ <div class="plugin-card-components">
433
+ <div class="component-badge" title="Agents">
434
+ <span class="component-icon">🤖</span>
435
+ <span class="component-value">${plugin.components.agents || 0}</span>
436
+ </div>
437
+ <div class="component-badge" title="Commands">
438
+ <span class="component-icon">⚡</span>
439
+ <span class="component-value">${plugin.components.commands || 0}</span>
440
+ </div>
441
+ <div class="component-badge" title="Hooks">
442
+ <span class="component-icon">🪝</span>
443
+ <span class="component-value">${plugin.components.hooks || 0}</span>
444
+ </div>
445
+ <div class="component-badge" title="MCPs">
446
+ <span class="component-icon">🔌</span>
447
+ <span class="component-value">${plugin.components.mcps || 0}</span>
448
+ </div>
449
+ </div>
450
+
451
+ <div class="plugin-card-footer">
452
+ <span class="plugin-card-marketplace">${this.escapeHtml(plugin.marketplace)}</span>
453
+ <span class="plugin-card-total">${totalComponents} component${totalComponents !== 1 ? 's' : ''}</span>
454
+ </div>
455
+ </article>
456
+ `;
457
+ }
458
+
459
+ renderPluginListItem(plugin) {
460
+ const statusClass = plugin.enabled ? 'enabled' : 'disabled';
461
+ const totalComponents = Object.values(plugin.components).reduce((sum, val) => sum + val, 0);
462
+
463
+ return `
464
+ <article class="plugin-list-item ${statusClass}" data-plugin="${this.escapeHtml(plugin.name)}">
465
+ <div class="plugin-list-status ${statusClass}"></div>
466
+
467
+ <div class="plugin-list-main">
468
+ <div class="plugin-list-info">
469
+ <h3 class="plugin-list-title">${this.escapeHtml(plugin.name)}</h3>
470
+ <p class="plugin-list-description">${this.escapeHtml(plugin.description)}</p>
471
+ </div>
472
+
473
+ <div class="plugin-list-meta">
474
+ <span class="plugin-list-version">v${plugin.version}</span>
475
+ <span class="plugin-list-marketplace">${this.escapeHtml(plugin.marketplace)}</span>
476
+ </div>
477
+ </div>
478
+
479
+ <div class="plugin-list-components">
480
+ <div class="component-badge-sm" title="Agents">
481
+ <span class="component-icon">🤖</span>
482
+ <span class="component-value">${plugin.components.agents || 0}</span>
483
+ </div>
484
+ <div class="component-badge-sm" title="Commands">
485
+ <span class="component-icon">⚡</span>
486
+ <span class="component-value">${plugin.components.commands || 0}</span>
487
+ </div>
488
+ <div class="component-badge-sm" title="Hooks">
489
+ <span class="component-icon">🪝</span>
490
+ <span class="component-value">${plugin.components.hooks || 0}</span>
491
+ </div>
492
+ <div class="component-badge-sm" title="MCPs">
493
+ <span class="component-icon">🔌</span>
494
+ <span class="component-value">${plugin.components.mcps || 0}</span>
495
+ </div>
496
+ </div>
497
+
498
+ <div class="plugin-list-action">
499
+ <span class="action-icon">→</span>
500
+ </div>
501
+ </article>
502
+ `;
503
+ }
504
+
505
+ showPluginDetails(pluginName) {
506
+ const plugin = this.data.plugins.find(p => p.name === pluginName);
507
+ if (!plugin) return;
508
+
509
+ document.getElementById('modalPluginName').textContent = plugin.name;
510
+ document.getElementById('modalPluginVersion').textContent = `v${plugin.version}`;
511
+
512
+ const statusBadge = document.getElementById('modalPluginStatus');
513
+ statusBadge.textContent = plugin.enabled ? 'Enabled' : 'Disabled';
514
+ statusBadge.className = `modal-badge status-badge ${plugin.enabled ? 'enabled' : 'disabled'}`;
515
+
516
+ document.getElementById('modalPluginDescription').textContent = plugin.description;
517
+
518
+ // Components
519
+ document.getElementById('modalComponents').innerHTML = `
520
+ <div class="modal-component-card">
521
+ <div class="modal-component-icon">🤖</div>
522
+ <div class="modal-component-count">${plugin.components.agents || 0}</div>
523
+ <div class="modal-component-label">Agents</div>
524
+ </div>
525
+ <div class="modal-component-card">
526
+ <div class="modal-component-icon">⚡</div>
527
+ <div class="modal-component-count">${plugin.components.commands || 0}</div>
528
+ <div class="modal-component-label">Commands</div>
529
+ </div>
530
+ <div class="modal-component-card">
531
+ <div class="modal-component-icon">🪝</div>
532
+ <div class="modal-component-count">${plugin.components.hooks || 0}</div>
533
+ <div class="modal-component-label">Hooks</div>
534
+ </div>
535
+ <div class="modal-component-card">
536
+ <div class="modal-component-icon">🔌</div>
537
+ <div class="modal-component-count">${plugin.components.mcps || 0}</div>
538
+ <div class="modal-component-label">MCPs</div>
539
+ </div>
540
+ `;
541
+
542
+ // Details
543
+ document.getElementById('modalMarketplace').textContent = plugin.marketplace;
544
+
545
+ // Conditional fields
546
+ const authorCard = document.getElementById('modalAuthorCard');
547
+ if (plugin.author) {
548
+ authorCard.style.display = 'flex';
549
+ document.getElementById('modalAuthor').textContent =
550
+ typeof plugin.author === 'object' ? plugin.author.name : plugin.author;
551
+ } else {
552
+ authorCard.style.display = 'none';
553
+ }
554
+
555
+ const categoryCard = document.getElementById('modalCategoryCard');
556
+ if (plugin.category) {
557
+ categoryCard.style.display = 'flex';
558
+ document.getElementById('modalCategory').textContent = plugin.category;
559
+ } else {
560
+ categoryCard.style.display = 'none';
561
+ }
562
+
563
+ const licenseCard = document.getElementById('modalLicenseCard');
564
+ if (plugin.license) {
565
+ licenseCard.style.display = 'flex';
566
+ document.getElementById('modalLicense').textContent =
567
+ typeof plugin.license === 'object' ? plugin.license.type : plugin.license;
568
+ } else {
569
+ licenseCard.style.display = 'none';
570
+ }
571
+
572
+ // Keywords
573
+ const keywordsSection = document.getElementById('modalKeywordsSection');
574
+ if (plugin.keywords && plugin.keywords.length > 0) {
575
+ keywordsSection.style.display = 'block';
576
+ document.getElementById('modalKeywords').innerHTML = plugin.keywords
577
+ .map(kw => `<span class="keyword-tag">${this.escapeHtml(kw)}</span>`)
578
+ .join('');
579
+ } else {
580
+ keywordsSection.style.display = 'none';
581
+ }
582
+
583
+ // Homepage
584
+ const homepageSection = document.getElementById('modalHomepageSection');
585
+ if (plugin.homepage) {
586
+ homepageSection.style.display = 'block';
587
+ document.getElementById('modalHomepage').href = plugin.homepage;
588
+ } else {
589
+ homepageSection.style.display = 'none';
590
+ }
591
+
592
+ // Render plugin actions and update command references
593
+ this.renderPluginActions(plugin);
594
+ this.updateCommandReferences(plugin);
595
+
596
+ // Show modal
597
+ document.getElementById('pluginModal').classList.add('active');
598
+ document.body.style.overflow = 'hidden';
599
+ }
600
+
601
+ // ==================== PLUGIN ACTIONS & COMMANDS ====================
602
+
603
+ renderPluginActions(plugin) {
604
+ const actionsContainer = document.getElementById('modalActions');
605
+ if (!actionsContainer) return;
606
+
607
+ const pluginIdentifier = `${plugin.name}@${plugin.marketplace}`;
608
+
609
+ // Determine which buttons to show based on plugin status
610
+ let buttons = [];
611
+
612
+ if (!plugin.enabled) {
613
+ // Plugin is disabled or not installed - show install button
614
+ buttons.push({
615
+ label: 'Install Plugin',
616
+ icon: '📥',
617
+ class: 'install-btn',
618
+ command: 'install'
619
+ });
620
+ } else {
621
+ // Plugin is enabled - show disable and uninstall buttons
622
+ buttons.push({
623
+ label: 'Disable Plugin',
624
+ icon: '⏸️',
625
+ class: 'disable-btn',
626
+ command: 'disable'
627
+ });
628
+ }
629
+
630
+ // Always show uninstall if enabled
631
+ if (plugin.enabled) {
632
+ buttons.push({
633
+ label: 'Uninstall Plugin',
634
+ icon: '🗑️',
635
+ class: 'uninstall-btn',
636
+ command: 'uninstall'
637
+ });
638
+ }
639
+
640
+ // If disabled but installed, show enable button
641
+ if (!plugin.enabled && this.isPluginInstalled(plugin)) {
642
+ buttons.unshift({
643
+ label: 'Enable Plugin',
644
+ icon: '✅',
645
+ class: 'enable-btn',
646
+ command: 'enable'
647
+ });
648
+ }
649
+
650
+ actionsContainer.innerHTML = buttons.map(btn => `
651
+ <button class="action-button ${btn.class}" onclick="window.dashboard.copyPluginCommand('${btn.command}', '${this.escapeHtml(pluginIdentifier)}')">
652
+ <span class="action-icon">${btn.icon}</span>
653
+ ${btn.label}
654
+ </button>
655
+ `).join('');
656
+ }
657
+
658
+ isPluginInstalled(plugin) {
659
+ // This is a simple heuristic - in a real implementation, you'd check the actual installation status
660
+ return plugin.components.agents > 0 || plugin.components.commands > 0 ||
661
+ plugin.components.hooks > 0 || plugin.components.mcps > 0;
662
+ }
663
+
664
+ updateCommandReferences(plugin) {
665
+ const pluginIdentifier = `${plugin.name}@${plugin.marketplace}`;
666
+
667
+ document.getElementById('installCommand').textContent = `/plugin install ${pluginIdentifier}`;
668
+ document.getElementById('enableCommand').textContent = `/plugin enable ${pluginIdentifier}`;
669
+ document.getElementById('disableCommand').textContent = `/plugin disable ${pluginIdentifier}`;
670
+ document.getElementById('uninstallCommand').textContent = `/plugin uninstall ${pluginIdentifier}`;
671
+
672
+ // Store current plugin for command copying
673
+ this.currentPlugin = plugin;
674
+ }
675
+
676
+ copyCommand(commandType) {
677
+ if (!this.currentPlugin) return;
678
+
679
+ const pluginIdentifier = `${this.currentPlugin.name}@${this.currentPlugin.marketplace}`;
680
+ const command = `/plugin ${commandType} ${pluginIdentifier}`;
681
+
682
+ this.copyToClipboard(command);
683
+ this.showToast(`Command copied: ${command}`);
684
+ }
685
+
686
+ copyPluginCommand(commandType, pluginIdentifier) {
687
+ const command = `/plugin ${commandType} ${pluginIdentifier}`;
688
+ this.copyToClipboard(command);
689
+ this.showToast(`Command copied: ${command}`);
690
+ }
691
+
692
+ copyToClipboard(text) {
693
+ if (navigator.clipboard && navigator.clipboard.writeText) {
694
+ navigator.clipboard.writeText(text)
695
+ .then(() => {
696
+ console.log('Command copied to clipboard:', text);
697
+ })
698
+ .catch(err => {
699
+ console.error('Failed to copy:', err);
700
+ this.fallbackCopy(text);
701
+ });
702
+ } else {
703
+ this.fallbackCopy(text);
704
+ }
705
+ }
706
+
707
+ fallbackCopy(text) {
708
+ const textarea = document.createElement('textarea');
709
+ textarea.value = text;
710
+ textarea.style.position = 'fixed';
711
+ textarea.style.opacity = '0';
712
+ document.body.appendChild(textarea);
713
+ textarea.select();
714
+
715
+ try {
716
+ document.execCommand('copy');
717
+ console.log('Command copied to clipboard (fallback):', text);
718
+ } catch (err) {
719
+ console.error('Fallback copy failed:', err);
720
+ }
721
+
722
+ document.body.removeChild(textarea);
723
+ }
724
+
725
+ showToast(message) {
726
+ const toast = document.getElementById('commandToast');
727
+ if (!toast) return;
728
+
729
+ toast.querySelector('.toast-message').textContent = message;
730
+ toast.classList.add('show');
731
+
732
+ setTimeout(() => {
733
+ toast.classList.remove('show');
734
+ }, 3000);
735
+ }
736
+
737
+ closeModal() {
738
+ document.getElementById('pluginModal').classList.remove('active');
739
+ document.body.style.overflow = '';
740
+ }
741
+
742
+ setViewMode(mode) {
743
+ this.state.viewMode = mode;
744
+
745
+ document.querySelectorAll('.view-btn').forEach(btn => {
746
+ btn.classList.toggle('active', btn.dataset.view === mode);
747
+ });
748
+
749
+ this.renderPlugins();
750
+ localStorage.setItem('pluginDashboardViewMode', mode);
751
+ }
752
+
753
+ toggleSidebar() {
754
+ this.state.sidebarCollapsed = !this.state.sidebarCollapsed;
755
+ const sidebar = document.getElementById('sidebar');
756
+ sidebar.classList.toggle('collapsed', this.state.sidebarCollapsed);
757
+ localStorage.setItem('sidebarCollapsed', this.state.sidebarCollapsed);
758
+ }
759
+
760
+ restoreViewPreferences() {
761
+ const savedViewMode = localStorage.getItem('pluginDashboardViewMode');
762
+ if (savedViewMode) {
763
+ this.setViewMode(savedViewMode);
764
+ }
765
+
766
+ const savedSidebarState = localStorage.getItem('sidebarCollapsed');
767
+ if (savedSidebarState === 'true') {
768
+ this.toggleSidebar();
769
+ }
770
+ }
771
+
772
+ clearAllFilters() {
773
+ this.state.selectedMarketplace = null;
774
+ this.state.pluginStatusFilter = 'all';
775
+ this.state.searchQuery = '';
776
+
777
+ document.getElementById('pluginSearch').value = '';
778
+
779
+ document.querySelectorAll('.filter-chip').forEach(chip => {
780
+ chip.classList.toggle('active', chip.dataset.filter === 'all');
781
+ });
782
+
783
+ this.renderMarketplaces();
784
+ this.renderPlugins();
785
+ this.updatePageTitle();
786
+ }
787
+
788
+ escapeHtml(text) {
789
+ if (typeof text !== 'string') return '';
790
+ const div = document.createElement('div');
791
+ div.textContent = text;
792
+ return div.innerHTML;
793
+ }
794
+
795
+ showError(message) {
796
+ console.error(message);
797
+ }
798
+ }
799
+
800
+ // Initialize dashboard
801
+ document.addEventListener('DOMContentLoaded', () => {
802
+ window.dashboard = new PluginDashboard();
803
+ window.dashboard.init();
804
+ });
805
+
806
+ // ==================== REFRESH FUNCTIONALITY ====================