claude-code-marketplace 0.2.1 → 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/package.json +1 -1
- package/public/app.js +36 -2
- package/public/index.html +4 -1
- package/public/style.css +24 -0
- 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/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();
|
|
@@ -388,7 +391,10 @@ async function showDetail(pluginId) {
|
|
|
388
391
|
panel.innerHTML = `
|
|
389
392
|
<div class="detail-header">
|
|
390
393
|
<h3>${headerIcon} ${esc(plugin.name)} ${plugin.version ? `<span class="version">v${esc(plugin.version)}</span>` : ''}</h3>
|
|
391
|
-
<
|
|
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>
|
|
392
398
|
</div>
|
|
393
399
|
<div class="detail-body">
|
|
394
400
|
${updateBanner}
|
|
@@ -503,6 +509,7 @@ const EXT_TO_LANG = {
|
|
|
503
509
|
|
|
504
510
|
const PREFERRED_FILE = 'SKILL.MD';
|
|
505
511
|
let _contentCodeEl = null;
|
|
512
|
+
let _contentPluginId = null;
|
|
506
513
|
|
|
507
514
|
function highlightSource(text, fileName) {
|
|
508
515
|
const ext = (fileName || '').split('.').pop().toLowerCase();
|
|
@@ -530,7 +537,31 @@ function getContentCodeEl() {
|
|
|
530
537
|
return _contentCodeEl;
|
|
531
538
|
}
|
|
532
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
|
+
|
|
533
563
|
async function openContentModal(pluginId, initialPath, componentType) {
|
|
564
|
+
_contentPluginId = pluginId;
|
|
534
565
|
const plugin = findPlugin(pluginId);
|
|
535
566
|
const label = COMP_LABELS[componentType] || componentType;
|
|
536
567
|
document.getElementById('contentModalTitle').textContent = `${plugin?.name || pluginId} \u2014 ${label}`;
|
|
@@ -649,7 +680,10 @@ function showMarketplaceDetail(name) {
|
|
|
649
680
|
panel.innerHTML = `
|
|
650
681
|
<div class="detail-header">
|
|
651
682
|
<h3>${ICONS.marketplace} ${esc(m.name)} ${m.version ? `<span class="version">v${esc(m.version)}</span>` : ''}</h3>
|
|
652
|
-
<
|
|
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>
|
|
653
687
|
</div>
|
|
654
688
|
<div class="detail-body">
|
|
655
689
|
<div class="detail-section">
|
package/public/index.html
CHANGED
|
@@ -135,7 +135,10 @@
|
|
|
135
135
|
<div class="modal content-modal" onclick="event.stopPropagation()">
|
|
136
136
|
<div class="modal-header">
|
|
137
137
|
<h3 id="contentModalTitle">File Preview</h3>
|
|
138
|
-
<
|
|
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>
|
|
139
142
|
</div>
|
|
140
143
|
<div class="content-modal-body">
|
|
141
144
|
<div class="content-tree" id="contentTree"></div>
|
package/public/style.css
CHANGED
|
@@ -720,6 +720,12 @@ body.light .scope-toggle.local {
|
|
|
720
720
|
font-size: 11px;
|
|
721
721
|
font-weight: 400;
|
|
722
722
|
}
|
|
723
|
+
.detail-header-actions,
|
|
724
|
+
.modal-header-actions {
|
|
725
|
+
display: flex;
|
|
726
|
+
align-items: center;
|
|
727
|
+
gap: 4px;
|
|
728
|
+
}
|
|
723
729
|
.detail-close {
|
|
724
730
|
background: transparent;
|
|
725
731
|
border: 1px solid transparent;
|
|
@@ -997,6 +1003,20 @@ body.light .scope-toggle.local {
|
|
|
997
1003
|
font-size: 13px;
|
|
998
1004
|
font-weight: 600;
|
|
999
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
|
+
}
|
|
1000
1020
|
.modal-close {
|
|
1001
1021
|
background: none;
|
|
1002
1022
|
border: none;
|
|
@@ -1004,6 +1024,10 @@ body.light .scope-toggle.local {
|
|
|
1004
1024
|
cursor: pointer;
|
|
1005
1025
|
font-size: 16px;
|
|
1006
1026
|
padding: 4px;
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
justify-content: center;
|
|
1030
|
+
line-height: 1;
|
|
1007
1031
|
}
|
|
1008
1032
|
.modal-body {
|
|
1009
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
|
});
|