claude-code-marketplace 0.2.0 → 0.3.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/LICENSE +21 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/public/app.js +59 -6
- package/public/index.html +5 -1
- package/public/style.css +58 -64
- package/server.js +65 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksii Nikiforov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A web-based dashboard for browsing, installing, and managing [Claude Code](https://docs.anthropic.com/en/docs/claude-code) plugins across multiple marketplaces.
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/claude-code-marketplace)
|
|
6
|
+
|
|
5
7
|
<p align="center">
|
|
6
8
|
<img src="assets/main-dark.png" alt="Marketplace — dark theme" width="100%">
|
|
7
9
|
</p>
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -43,6 +43,8 @@ const ICONS = {
|
|
|
43
43
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="12" cy="5" r="2.5"/><circle cx="12" cy="12" r="2.5"/><circle cx="12" cy="19" r="2.5"/></svg>',
|
|
44
44
|
};
|
|
45
45
|
ICONS.settings = ICONS.gear;
|
|
46
|
+
ICONS.openEditor =
|
|
47
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M17.583 2.207a1.1 1.1 0 0 1 1.541.033l2.636 2.636a1.1 1.1 0 0 1 .033 1.541L10.68 17.53a1.1 1.1 0 0 1-.345.247l-4.56 1.903a.55.55 0 0 1-.725-.725l1.903-4.56a1.1 1.1 0 0 1 .247-.345zm.902 1.87-8.794 8.793-.946 2.268 2.268-.946 8.794-8.793z"/></svg>';
|
|
46
48
|
const COMP_HAS_DIR = new Set(['skills', 'commands', 'agents']);
|
|
47
49
|
const COMP_LABELS = {
|
|
48
50
|
skills: 'Skills',
|
|
@@ -65,6 +67,7 @@ function updateArrow(p) {
|
|
|
65
67
|
|
|
66
68
|
// --- Init ---
|
|
67
69
|
document.addEventListener('DOMContentLoaded', () => {
|
|
70
|
+
document.getElementById('contentOpenEditor').innerHTML = ICONS.openEditor;
|
|
68
71
|
restoreAppState();
|
|
69
72
|
loadProject();
|
|
70
73
|
loadData();
|
|
@@ -101,10 +104,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
101
104
|
});
|
|
102
105
|
|
|
103
106
|
const savedTheme = localStorage.getItem('theme');
|
|
104
|
-
if (savedTheme === '
|
|
105
|
-
document.body.classList.add('light');
|
|
106
|
-
} else if (savedTheme === 'dark') {
|
|
107
|
+
if (savedTheme === 'dark') {
|
|
107
108
|
document.body.classList.add('dark-forced');
|
|
109
|
+
} else {
|
|
110
|
+
document.body.classList.add('light');
|
|
108
111
|
}
|
|
109
112
|
syncHljsTheme();
|
|
110
113
|
|
|
@@ -225,7 +228,9 @@ function renderTree() {
|
|
|
225
228
|
return;
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
|
|
231
|
+
const allIds = marketplaces.map((m) => `m_${safeId(m.name)}`);
|
|
232
|
+
const allExpanded = allIds.length > 0 && allIds.every((id) => expandedNodes.has(id));
|
|
233
|
+
let html = `<div class="tree-expand-toggle" id="toggleExpandBtn" onclick="toggleExpandAll()">${allExpanded ? 'Collapse All' : 'Expand All'}</div>`;
|
|
229
234
|
|
|
230
235
|
for (const m of marketplaces) {
|
|
231
236
|
const mid = safeId(m.name);
|
|
@@ -386,7 +391,10 @@ async function showDetail(pluginId) {
|
|
|
386
391
|
panel.innerHTML = `
|
|
387
392
|
<div class="detail-header">
|
|
388
393
|
<h3>${headerIcon} ${esc(plugin.name)} ${plugin.version ? `<span class="version">v${esc(plugin.version)}</span>` : ''}</h3>
|
|
389
|
-
<
|
|
394
|
+
<div class="detail-header-actions">
|
|
395
|
+
${plugin._pluginDir ? `<button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({pluginId:'${esc(plugin.fullId)}'})">${ICONS.openEditor}</button>` : ''}
|
|
396
|
+
<button class="detail-close" onclick="closeDetail()">\u2715</button>
|
|
397
|
+
</div>
|
|
390
398
|
</div>
|
|
391
399
|
<div class="detail-body">
|
|
392
400
|
${updateBanner}
|
|
@@ -501,6 +509,7 @@ const EXT_TO_LANG = {
|
|
|
501
509
|
|
|
502
510
|
const PREFERRED_FILE = 'SKILL.MD';
|
|
503
511
|
let _contentCodeEl = null;
|
|
512
|
+
let _contentPluginId = null;
|
|
504
513
|
|
|
505
514
|
function highlightSource(text, fileName) {
|
|
506
515
|
const ext = (fileName || '').split('.').pop().toLowerCase();
|
|
@@ -528,7 +537,31 @@ function getContentCodeEl() {
|
|
|
528
537
|
return _contentCodeEl;
|
|
529
538
|
}
|
|
530
539
|
|
|
540
|
+
async function openInEditor() {
|
|
541
|
+
if (!_contentPluginId) return;
|
|
542
|
+
const relativePath = document.getElementById('contentViewerPath').textContent || '';
|
|
543
|
+
try {
|
|
544
|
+
await fetch('/api/open-in-editor', {
|
|
545
|
+
method: 'POST',
|
|
546
|
+
headers: { 'Content-Type': 'application/json' },
|
|
547
|
+
body: JSON.stringify({ pluginId: _contentPluginId, relativePath }),
|
|
548
|
+
});
|
|
549
|
+
} catch {}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function openFolderInEditor({ pluginId, marketplaceName } = {}) {
|
|
553
|
+
if (!pluginId && !marketplaceName) return;
|
|
554
|
+
try {
|
|
555
|
+
await fetch('/api/open-folder-in-editor', {
|
|
556
|
+
method: 'POST',
|
|
557
|
+
headers: { 'Content-Type': 'application/json' },
|
|
558
|
+
body: JSON.stringify({ pluginId, marketplaceName }),
|
|
559
|
+
});
|
|
560
|
+
} catch {}
|
|
561
|
+
}
|
|
562
|
+
|
|
531
563
|
async function openContentModal(pluginId, initialPath, componentType) {
|
|
564
|
+
_contentPluginId = pluginId;
|
|
532
565
|
const plugin = findPlugin(pluginId);
|
|
533
566
|
const label = COMP_LABELS[componentType] || componentType;
|
|
534
567
|
document.getElementById('contentModalTitle').textContent = `${plugin?.name || pluginId} \u2014 ${label}`;
|
|
@@ -647,7 +680,10 @@ function showMarketplaceDetail(name) {
|
|
|
647
680
|
panel.innerHTML = `
|
|
648
681
|
<div class="detail-header">
|
|
649
682
|
<h3>${ICONS.marketplace} ${esc(m.name)} ${m.version ? `<span class="version">v${esc(m.version)}</span>` : ''}</h3>
|
|
650
|
-
<
|
|
683
|
+
<div class="detail-header-actions">
|
|
684
|
+
${m.installLocation ? `<button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({marketplaceName:'${esc(m.name)}'})">${ICONS.openEditor}</button>` : ''}
|
|
685
|
+
<button class="detail-close" onclick="closeDetail()">\u2715</button>
|
|
686
|
+
</div>
|
|
651
687
|
</div>
|
|
652
688
|
<div class="detail-body">
|
|
653
689
|
<div class="detail-section">
|
|
@@ -810,6 +846,17 @@ function toggleChildren(id) {
|
|
|
810
846
|
}
|
|
811
847
|
}
|
|
812
848
|
|
|
849
|
+
function toggleExpandAll() {
|
|
850
|
+
const allIds = marketplaces.map((m) => `m_${safeId(m.name)}`);
|
|
851
|
+
const collapse = allIds.length > 0 && allIds.every((id) => expandedNodes.has(id));
|
|
852
|
+
for (const id of allIds) {
|
|
853
|
+
if (collapse) expandedNodes.delete(id);
|
|
854
|
+
else expandedNodes.add(id);
|
|
855
|
+
}
|
|
856
|
+
saveExpandedNodes();
|
|
857
|
+
renderTree();
|
|
858
|
+
}
|
|
859
|
+
|
|
813
860
|
function findPlugin(id) {
|
|
814
861
|
for (const m of marketplaces) {
|
|
815
862
|
const p = m.plugins.find((p) => p.fullId === id);
|
|
@@ -998,6 +1045,12 @@ function handleKeydown(e) {
|
|
|
998
1045
|
return;
|
|
999
1046
|
}
|
|
1000
1047
|
|
|
1048
|
+
if (matchKey(e, 'e')) {
|
|
1049
|
+
e.preventDefault();
|
|
1050
|
+
toggleExpandAll();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1001
1054
|
if (matchKey(e, 'r')) {
|
|
1002
1055
|
e.preventDefault();
|
|
1003
1056
|
refresh();
|
package/public/index.html
CHANGED
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
<tr><td><kbd>?</kbd></td><td>Show keyboard shortcuts</td></tr>
|
|
111
111
|
<tr><td><kbd>/</kbd></td><td>Focus search</td></tr>
|
|
112
112
|
<tr><td><kbd>S</kbd></td><td>Focus scope filter</td></tr>
|
|
113
|
+
<tr><td><kbd>E</kbd></td><td>Expand / Collapse all</td></tr>
|
|
113
114
|
<tr><td><kbd>R</kbd></td><td>Refresh data</td></tr>
|
|
114
115
|
<tr><td><kbd>Esc</kbd></td><td>Close panel / blur input</td></tr>
|
|
115
116
|
</table>
|
|
@@ -134,7 +135,10 @@
|
|
|
134
135
|
<div class="modal content-modal" onclick="event.stopPropagation()">
|
|
135
136
|
<div class="modal-header">
|
|
136
137
|
<h3 id="contentModalTitle">File Preview</h3>
|
|
137
|
-
<
|
|
138
|
+
<div class="modal-header-actions">
|
|
139
|
+
<button class="modal-action-btn" id="contentOpenEditor" title="Open in VS Code" onclick="openInEditor()"></button>
|
|
140
|
+
<button class="modal-close" onclick="closeModal('contentModal')">✕</button>
|
|
141
|
+
</div>
|
|
138
142
|
</div>
|
|
139
143
|
<div class="content-modal-body">
|
|
140
144
|
<div class="content-tree" id="contentTree"></div>
|
package/public/style.css
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
--scope-user-dim: rgba(196, 149, 106, 0.18);
|
|
32
32
|
--scope-project: #7ab5a0;
|
|
33
33
|
--scope-project-dim: rgba(122, 181, 160, 0.18);
|
|
34
|
-
--scope-local: #
|
|
35
|
-
--scope-local-dim: rgba(
|
|
34
|
+
--scope-local: #8a9bb5;
|
|
35
|
+
--scope-local-dim: rgba(138, 155, 181, 0.18);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
body.light {
|
|
@@ -57,43 +57,12 @@ body.light {
|
|
|
57
57
|
--plan: #5a7a5a;
|
|
58
58
|
--plan-dim: rgba(90, 122, 90, 0.15);
|
|
59
59
|
|
|
60
|
-
--scope-user: #
|
|
61
|
-
--scope-user-dim: rgba(
|
|
60
|
+
--scope-user: #8a7055;
|
|
61
|
+
--scope-user-dim: rgba(138, 112, 85, 0.12);
|
|
62
62
|
--scope-project: #4d8a72;
|
|
63
63
|
--scope-project-dim: rgba(77, 138, 114, 0.15);
|
|
64
|
-
--scope-local: #
|
|
65
|
-
--scope-local-dim: rgba(
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@media (prefers-color-scheme: light) {
|
|
69
|
-
body:not(.dark-forced) {
|
|
70
|
-
--bg-deep: #e8e6e3;
|
|
71
|
-
--bg-surface: #f4f3f1;
|
|
72
|
-
--bg-elevated: #dddbd8;
|
|
73
|
-
--bg-hover: #d2d0cc;
|
|
74
|
-
--border: #a09b94;
|
|
75
|
-
--text-primary: #0a0a0a;
|
|
76
|
-
--text-secondary: #444444;
|
|
77
|
-
--text-tertiary: #666666;
|
|
78
|
-
--text-muted: #888888;
|
|
79
|
-
--accent-text: #b85a20;
|
|
80
|
-
--accent-dim: rgba(232, 111, 51, 0.18);
|
|
81
|
-
--accent-glow: rgba(232, 111, 51, 0.5);
|
|
82
|
-
--success: #1a8a5a;
|
|
83
|
-
--success-dim: rgba(26, 138, 90, 0.15);
|
|
84
|
-
--warning: #b07d0a;
|
|
85
|
-
--warning-dim: rgba(176, 125, 10, 0.15);
|
|
86
|
-
--error: #c53030;
|
|
87
|
-
--error-dim: rgba(197, 48, 48, 0.15);
|
|
88
|
-
--plan: #5a7a5a;
|
|
89
|
-
--plan-dim: rgba(90, 122, 90, 0.15);
|
|
90
|
-
--scope-user: #9a6d3a;
|
|
91
|
-
--scope-user-dim: rgba(154, 109, 58, 0.15);
|
|
92
|
-
--scope-project: #4d8a72;
|
|
93
|
-
--scope-project-dim: rgba(77, 138, 114, 0.15);
|
|
94
|
-
--scope-local: #b07d0a;
|
|
95
|
-
--scope-local-dim: rgba(176, 125, 10, 0.15);
|
|
96
|
-
}
|
|
64
|
+
--scope-local: #6b7d96;
|
|
65
|
+
--scope-local-dim: rgba(107, 125, 150, 0.12);
|
|
97
66
|
}
|
|
98
67
|
|
|
99
68
|
/* === RESET === */
|
|
@@ -110,7 +79,7 @@ body {
|
|
|
110
79
|
background: var(--bg-deep);
|
|
111
80
|
color: var(--text-primary);
|
|
112
81
|
line-height: 1.5;
|
|
113
|
-
height: 100vh;
|
|
82
|
+
height: calc(100vh / 1.2);
|
|
114
83
|
display: flex;
|
|
115
84
|
zoom: 1.2;
|
|
116
85
|
flex-direction: column;
|
|
@@ -303,6 +272,7 @@ body {
|
|
|
303
272
|
.main-layout {
|
|
304
273
|
display: flex;
|
|
305
274
|
flex: 1;
|
|
275
|
+
min-height: 0;
|
|
306
276
|
overflow: hidden;
|
|
307
277
|
}
|
|
308
278
|
|
|
@@ -316,8 +286,22 @@ body {
|
|
|
316
286
|
min-width: 400px;
|
|
317
287
|
}
|
|
318
288
|
|
|
289
|
+
.tree-expand-toggle {
|
|
290
|
+
padding: 4px 12px;
|
|
291
|
+
font-size: 10px;
|
|
292
|
+
color: var(--text-muted);
|
|
293
|
+
cursor: pointer;
|
|
294
|
+
text-transform: uppercase;
|
|
295
|
+
letter-spacing: 0.5px;
|
|
296
|
+
font-family: var(--mono);
|
|
297
|
+
}
|
|
298
|
+
.tree-expand-toggle:hover {
|
|
299
|
+
color: var(--accent);
|
|
300
|
+
}
|
|
301
|
+
|
|
319
302
|
.tree-container {
|
|
320
303
|
flex: 1;
|
|
304
|
+
min-height: 0;
|
|
321
305
|
overflow-y: auto;
|
|
322
306
|
overflow-x: hidden;
|
|
323
307
|
}
|
|
@@ -459,65 +443,51 @@ body {
|
|
|
459
443
|
border-color: rgba(196, 149, 106, 0.4);
|
|
460
444
|
color: rgba(196, 149, 106, 0.5);
|
|
461
445
|
}
|
|
462
|
-
.scope-toggle.user.active
|
|
463
|
-
background: var(--scope-user-dim);
|
|
464
|
-
color: var(--scope-user);
|
|
465
|
-
border-color: var(--scope-user);
|
|
466
|
-
}
|
|
446
|
+
.scope-toggle.user.active,
|
|
467
447
|
.scope-toggle.user.disabled {
|
|
468
448
|
background: var(--scope-user-dim);
|
|
469
449
|
color: var(--scope-user);
|
|
470
450
|
border-color: var(--scope-user);
|
|
471
|
-
opacity: 0.4;
|
|
472
|
-
text-decoration: line-through;
|
|
473
451
|
}
|
|
474
452
|
|
|
475
453
|
.scope-toggle.project {
|
|
476
454
|
border-color: rgba(122, 181, 160, 0.4);
|
|
477
455
|
color: rgba(122, 181, 160, 0.5);
|
|
478
456
|
}
|
|
479
|
-
.scope-toggle.project.active
|
|
480
|
-
background: var(--scope-project-dim);
|
|
481
|
-
color: var(--scope-project);
|
|
482
|
-
border-color: var(--scope-project);
|
|
483
|
-
}
|
|
457
|
+
.scope-toggle.project.active,
|
|
484
458
|
.scope-toggle.project.disabled {
|
|
485
459
|
background: var(--scope-project-dim);
|
|
486
460
|
color: var(--scope-project);
|
|
487
461
|
border-color: var(--scope-project);
|
|
488
|
-
opacity: 0.4;
|
|
489
|
-
text-decoration: line-through;
|
|
490
462
|
}
|
|
491
463
|
|
|
492
464
|
.scope-toggle.local {
|
|
493
|
-
border-color: rgba(
|
|
494
|
-
color: rgba(
|
|
495
|
-
}
|
|
496
|
-
.scope-toggle.local.active {
|
|
497
|
-
background: var(--scope-local-dim);
|
|
498
|
-
color: var(--scope-local);
|
|
499
|
-
border-color: var(--scope-local);
|
|
465
|
+
border-color: rgba(138, 155, 181, 0.4);
|
|
466
|
+
color: rgba(138, 155, 181, 0.5);
|
|
500
467
|
}
|
|
468
|
+
.scope-toggle.local.active,
|
|
501
469
|
.scope-toggle.local.disabled {
|
|
502
470
|
background: var(--scope-local-dim);
|
|
503
471
|
color: var(--scope-local);
|
|
504
472
|
border-color: var(--scope-local);
|
|
473
|
+
}
|
|
474
|
+
.scope-toggle.disabled {
|
|
505
475
|
opacity: 0.4;
|
|
506
476
|
text-decoration: line-through;
|
|
507
477
|
}
|
|
508
478
|
|
|
509
479
|
/* Light theme: boost inactive scope contrast further */
|
|
510
480
|
body.light .scope-toggle.user {
|
|
511
|
-
border-color: rgba(
|
|
512
|
-
color: rgba(
|
|
481
|
+
border-color: rgba(138, 112, 85, 0.4);
|
|
482
|
+
color: rgba(138, 112, 85, 0.5);
|
|
513
483
|
}
|
|
514
484
|
body.light .scope-toggle.project {
|
|
515
485
|
border-color: rgba(77, 138, 114, 0.5);
|
|
516
486
|
color: rgba(77, 138, 114, 0.6);
|
|
517
487
|
}
|
|
518
488
|
body.light .scope-toggle.local {
|
|
519
|
-
border-color: rgba(
|
|
520
|
-
color: rgba(
|
|
489
|
+
border-color: rgba(107, 125, 150, 0.4);
|
|
490
|
+
color: rgba(107, 125, 150, 0.5);
|
|
521
491
|
}
|
|
522
492
|
|
|
523
493
|
/* === BADGES === */
|
|
@@ -652,7 +622,7 @@ body.light .scope-toggle.local {
|
|
|
652
622
|
.tree-desc-inline {
|
|
653
623
|
font-size: 11px;
|
|
654
624
|
color: var(--text-muted);
|
|
655
|
-
opacity: 0.
|
|
625
|
+
opacity: 0.95;
|
|
656
626
|
white-space: nowrap;
|
|
657
627
|
overflow: hidden;
|
|
658
628
|
text-overflow: ellipsis;
|
|
@@ -750,6 +720,12 @@ body.light .scope-toggle.local {
|
|
|
750
720
|
font-size: 11px;
|
|
751
721
|
font-weight: 400;
|
|
752
722
|
}
|
|
723
|
+
.detail-header-actions,
|
|
724
|
+
.modal-header-actions {
|
|
725
|
+
display: flex;
|
|
726
|
+
align-items: center;
|
|
727
|
+
gap: 4px;
|
|
728
|
+
}
|
|
753
729
|
.detail-close {
|
|
754
730
|
background: transparent;
|
|
755
731
|
border: 1px solid transparent;
|
|
@@ -1027,6 +1003,20 @@ body.light .scope-toggle.local {
|
|
|
1027
1003
|
font-size: 13px;
|
|
1028
1004
|
font-weight: 600;
|
|
1029
1005
|
}
|
|
1006
|
+
.modal-action-btn {
|
|
1007
|
+
background: none;
|
|
1008
|
+
border: none;
|
|
1009
|
+
color: var(--text-muted);
|
|
1010
|
+
cursor: pointer;
|
|
1011
|
+
padding: 4px;
|
|
1012
|
+
display: flex;
|
|
1013
|
+
align-items: center;
|
|
1014
|
+
border-radius: 4px;
|
|
1015
|
+
}
|
|
1016
|
+
.modal-action-btn:hover {
|
|
1017
|
+
color: var(--accent);
|
|
1018
|
+
background: var(--hover);
|
|
1019
|
+
}
|
|
1030
1020
|
.modal-close {
|
|
1031
1021
|
background: none;
|
|
1032
1022
|
border: none;
|
|
@@ -1034,6 +1024,10 @@ body.light .scope-toggle.local {
|
|
|
1034
1024
|
cursor: pointer;
|
|
1035
1025
|
font-size: 16px;
|
|
1036
1026
|
padding: 4px;
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
justify-content: center;
|
|
1030
|
+
line-height: 1;
|
|
1037
1031
|
}
|
|
1038
1032
|
.modal-body {
|
|
1039
1033
|
padding: 16px;
|
package/server.js
CHANGED
|
@@ -202,6 +202,21 @@ function loadMarketplaces() {
|
|
|
202
202
|
|
|
203
203
|
const compKeys = ['skills', 'commands', 'agents', 'mcpServers', 'hooks', 'lspServers'];
|
|
204
204
|
|
|
205
|
+
// Resolve origin dir from marketplace source
|
|
206
|
+
let originDir = null;
|
|
207
|
+
if (installLocation) {
|
|
208
|
+
const rawSource = pd.source;
|
|
209
|
+
if (typeof rawSource === 'string' && rawSource) {
|
|
210
|
+
const srcDir = path.resolve(installLocation, rawSource);
|
|
211
|
+
if (fs.existsSync(srcDir)) originDir = srcDir;
|
|
212
|
+
}
|
|
213
|
+
if (!originDir) {
|
|
214
|
+
const pluginSubdir = path.join(installLocation, 'plugins', pd.name);
|
|
215
|
+
if (fs.existsSync(pluginSubdir)) originDir = pluginSubdir;
|
|
216
|
+
else if ((mData.plugins || []).length === 1) originDir = installLocation;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
205
220
|
// Resolve plugin dir for filesystem-based component counts
|
|
206
221
|
let pluginDir = null;
|
|
207
222
|
for (const s of ['user', 'project', 'local']) {
|
|
@@ -209,11 +224,7 @@ function loadMarketplaces() {
|
|
|
209
224
|
const resolved = resolveInstallPath(ip);
|
|
210
225
|
if (resolved) { pluginDir = resolved; break; }
|
|
211
226
|
}
|
|
212
|
-
if (!pluginDir
|
|
213
|
-
const pluginSubdir = path.join(installLocation, 'plugins', pd.name);
|
|
214
|
-
if (fs.existsSync(pluginSubdir)) pluginDir = pluginSubdir;
|
|
215
|
-
else if ((mData.plugins || []).length === 1) pluginDir = installLocation;
|
|
216
|
-
}
|
|
227
|
+
if (!pluginDir) pluginDir = originDir;
|
|
217
228
|
|
|
218
229
|
const fsComps = pluginDir ? countComponents(pluginDir) : null;
|
|
219
230
|
const components = {};
|
|
@@ -253,6 +264,7 @@ function loadMarketplaces() {
|
|
|
253
264
|
installedScopes,
|
|
254
265
|
components,
|
|
255
266
|
_pluginDir: pluginDir,
|
|
267
|
+
_originDir: originDir,
|
|
256
268
|
_fsComps: fsComps,
|
|
257
269
|
metadata: Object.fromEntries(
|
|
258
270
|
Object.entries(pd).filter(([k]) => !['name', 'description', 'source', 'version', ...compKeys].includes(k))
|
|
@@ -562,6 +574,54 @@ app.get('/api/plugins/:pluginId/preview/*', (req, res) => {
|
|
|
562
574
|
}
|
|
563
575
|
});
|
|
564
576
|
|
|
577
|
+
function openVSCode(args, res) {
|
|
578
|
+
execFile('code', args, { shell: true }, (err) => {
|
|
579
|
+
if (err) return res.status(500).json({ error: 'Failed to open editor' });
|
|
580
|
+
res.json({ ok: true });
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
app.post('/api/open-in-editor', (req, res) => {
|
|
585
|
+
const { pluginId, relativePath } = req.body;
|
|
586
|
+
if (!pluginId) return res.status(400).json({ error: 'pluginId required' });
|
|
587
|
+
|
|
588
|
+
const marketplaces = getCachedMarketplaces();
|
|
589
|
+
const pluginDir = resolvePluginDir(pluginId, marketplaces);
|
|
590
|
+
if (!pluginDir) return res.status(404).json({ error: 'Plugin not found' });
|
|
591
|
+
|
|
592
|
+
const args = ['-n', pluginDir];
|
|
593
|
+
|
|
594
|
+
const pluginJson = path.join(pluginDir, '.claude-plugin', 'plugin.json');
|
|
595
|
+
if (fs.existsSync(pluginJson)) args.push(pluginJson);
|
|
596
|
+
|
|
597
|
+
if (relativePath) {
|
|
598
|
+
const fullPath = path.resolve(pluginDir, relativePath);
|
|
599
|
+
if (fullPath.startsWith(path.resolve(pluginDir))) {
|
|
600
|
+
args.push(fullPath);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
openVSCode(args, res);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
app.post('/api/open-folder-in-editor', (req, res) => {
|
|
608
|
+
const { pluginId, marketplaceName } = req.body;
|
|
609
|
+
const marketplaces = getCachedMarketplaces();
|
|
610
|
+
let folder;
|
|
611
|
+
|
|
612
|
+
if (pluginId) {
|
|
613
|
+
const plugin = findPlugin(pluginId, marketplaces);
|
|
614
|
+
folder = plugin?._originDir || plugin?._pluginDir || null;
|
|
615
|
+
} else if (marketplaceName) {
|
|
616
|
+
const m = marketplaces.find(m => m.name === marketplaceName);
|
|
617
|
+
folder = m?.installLocation || null;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (!folder) return res.status(404).json({ error: 'Directory not found' });
|
|
621
|
+
|
|
622
|
+
openVSCode(['-n', folder], res);
|
|
623
|
+
});
|
|
624
|
+
|
|
565
625
|
app.get('/api/project', (req, res) => {
|
|
566
626
|
res.json({ path: projectPath });
|
|
567
627
|
});
|