claude-code-marketplace 0.5.4 → 0.5.6
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/package.json +1 -1
- package/public/app.js +33 -8
- package/server.js +27 -6
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -46,6 +46,7 @@ ICONS.readme = SVG(
|
|
|
46
46
|
'<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/>',
|
|
47
47
|
);
|
|
48
48
|
ICONS.settings = ICONS.gear;
|
|
49
|
+
ICONS.claudeMd = ICONS.readme;
|
|
49
50
|
ICONS.openEditor =
|
|
50
51
|
'<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>';
|
|
51
52
|
ICONS.copyPath =
|
|
@@ -59,9 +60,26 @@ const COMP_LABELS = {
|
|
|
59
60
|
hooks: 'Hooks',
|
|
60
61
|
lspServers: 'LSP Servers',
|
|
61
62
|
settings: 'Settings',
|
|
63
|
+
claudeMd: 'CLAUDE.md',
|
|
62
64
|
readme: 'README',
|
|
63
65
|
};
|
|
64
66
|
|
|
67
|
+
const VIRTUAL_ROOT_PREFIX = '~root/';
|
|
68
|
+
|
|
69
|
+
function encodePathSegments(p) {
|
|
70
|
+
return p.split('/').map(encodeURIComponent).join('/');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getContentRelativePath() {
|
|
74
|
+
const el = document.getElementById('contentViewerPath');
|
|
75
|
+
return el.dataset.rawPath || el.textContent || '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function claudeMdLabel(name, count) {
|
|
79
|
+
if (name.startsWith(VIRTUAL_ROOT_PREFIX)) return count > 1 ? 'CLAUDE.md (project root)' : 'CLAUDE.md';
|
|
80
|
+
return count > 1 ? 'CLAUDE.md (.claude/)' : name;
|
|
81
|
+
}
|
|
82
|
+
|
|
65
83
|
function updateArrow(p) {
|
|
66
84
|
if (!p.hasUpdate) return '';
|
|
67
85
|
const title =
|
|
@@ -376,8 +394,8 @@ function renderPluginRow(p) {
|
|
|
376
394
|
<span class="tree-icon">${icon}</span>
|
|
377
395
|
<span class="tree-label">${esc(p.name)} ${ver} ${updateIndicator}</span>
|
|
378
396
|
${desc}
|
|
379
|
-
${scopes}
|
|
380
397
|
${summary}
|
|
398
|
+
${scopes}
|
|
381
399
|
</div>`;
|
|
382
400
|
|
|
383
401
|
return html;
|
|
@@ -601,7 +619,7 @@ function renderDetailComponents(pluginId, comps, hasDirAccess) {
|
|
|
601
619
|
: '';
|
|
602
620
|
html += `<div class="detail-comp-item${cls}"${click}>
|
|
603
621
|
<span class="icon">${type === 'skills' ? ICONS.folder : ICONS.file}</span>
|
|
604
|
-
${esc(name)}
|
|
622
|
+
${esc(type === 'claudeMd' ? claudeMdLabel(name, names.length) : name)}
|
|
605
623
|
</div>`;
|
|
606
624
|
}
|
|
607
625
|
html += '</div>';
|
|
@@ -674,7 +692,7 @@ async function postAndFlash(endpoint, data, btn) {
|
|
|
674
692
|
|
|
675
693
|
async function openInEditor(event) {
|
|
676
694
|
if (!_contentPluginId) return;
|
|
677
|
-
const relativePath =
|
|
695
|
+
const relativePath = getContentRelativePath();
|
|
678
696
|
await postAndFlash('/api/open-in-editor', { pluginId: _contentPluginId, relativePath }, event?.currentTarget);
|
|
679
697
|
}
|
|
680
698
|
|
|
@@ -710,8 +728,14 @@ async function copyToClipboard(text, btn) {
|
|
|
710
728
|
|
|
711
729
|
async function copyContentPath(event) {
|
|
712
730
|
if (!_contentPluginDir) return;
|
|
713
|
-
const relativePath =
|
|
714
|
-
|
|
731
|
+
const relativePath = getContentRelativePath();
|
|
732
|
+
let full;
|
|
733
|
+
if (relativePath.startsWith(VIRTUAL_ROOT_PREFIX)) {
|
|
734
|
+
const parentDir = _contentPluginDir.replace(/\/[^/]+\/?$/, '');
|
|
735
|
+
full = `${parentDir}/${relativePath.slice(VIRTUAL_ROOT_PREFIX.length)}`;
|
|
736
|
+
} else {
|
|
737
|
+
full = relativePath ? `${_contentPluginDir}/${relativePath}` : _contentPluginDir;
|
|
738
|
+
}
|
|
715
739
|
await copyToClipboard(full, event?.currentTarget);
|
|
716
740
|
}
|
|
717
741
|
|
|
@@ -766,7 +790,7 @@ async function openContentModal(pluginId, initialPath, componentType) {
|
|
|
766
790
|
|
|
767
791
|
async function loadContentTree(pluginId, treePath, container, depth, autoSelect) {
|
|
768
792
|
try {
|
|
769
|
-
const res = await fetch(`/api/plugins/${encodeURIComponent(pluginId)}/preview/${treePath}`);
|
|
793
|
+
const res = await fetch(`/api/plugins/${encodeURIComponent(pluginId)}/preview/${encodePathSegments(treePath)}`);
|
|
770
794
|
if (!res.ok) throw new Error('Not found');
|
|
771
795
|
const data = await res.json();
|
|
772
796
|
|
|
@@ -816,7 +840,8 @@ async function loadContentTree(pluginId, treePath, container, depth, autoSelect)
|
|
|
816
840
|
async function loadContentFile(pluginId, filePath) {
|
|
817
841
|
const codeEl = getContentCodeEl();
|
|
818
842
|
const pathEl = document.getElementById('contentViewerPath');
|
|
819
|
-
pathEl.textContent = filePath;
|
|
843
|
+
pathEl.textContent = filePath.startsWith(VIRTUAL_ROOT_PREFIX) ? filePath.slice(VIRTUAL_ROOT_PREFIX.length) : filePath;
|
|
844
|
+
pathEl.dataset.rawPath = filePath;
|
|
820
845
|
codeEl.innerHTML = '<span style="color:var(--text-dim)">Loading...</span>';
|
|
821
846
|
|
|
822
847
|
document.querySelectorAll('#contentTree .content-tree-item.active').forEach((el) => el.classList.remove('active'));
|
|
@@ -824,7 +849,7 @@ async function loadContentFile(pluginId, filePath) {
|
|
|
824
849
|
if (activeItem) activeItem.classList.add('active');
|
|
825
850
|
|
|
826
851
|
try {
|
|
827
|
-
const res = await fetch(`/api/plugins/${encodeURIComponent(pluginId)}/preview/${filePath}`);
|
|
852
|
+
const res = await fetch(`/api/plugins/${encodeURIComponent(pluginId)}/preview/${encodePathSegments(filePath)}`);
|
|
828
853
|
if (!res.ok) throw new Error('Not found');
|
|
829
854
|
const data = await res.json();
|
|
830
855
|
|
package/server.js
CHANGED
|
@@ -452,6 +452,15 @@ function scanCustomizations(basePath, scope) {
|
|
|
452
452
|
if (fs.existsSync(path.join(basePath, 'settings.local.json'))) settingsFiles.push('settings.local.json');
|
|
453
453
|
if (settingsFiles.length) components.settings = settingsFiles;
|
|
454
454
|
|
|
455
|
+
// Add CLAUDE.md files as browsable entries
|
|
456
|
+
const claudeMdFiles = [];
|
|
457
|
+
if (fs.existsSync(path.join(basePath, 'CLAUDE.md'))) claudeMdFiles.push('CLAUDE.md');
|
|
458
|
+
if (scope === 'project') {
|
|
459
|
+
const parentClaude = path.join(basePath, '..', 'CLAUDE.md');
|
|
460
|
+
if (fs.existsSync(parentClaude)) claudeMdFiles.push('~root/CLAUDE.md');
|
|
461
|
+
}
|
|
462
|
+
if (claudeMdFiles.length) components.claudeMd = claudeMdFiles;
|
|
463
|
+
|
|
455
464
|
const hasAny = Object.values(components).some(v => Array.isArray(v) && v.length > 0);
|
|
456
465
|
if (!hasAny) return null;
|
|
457
466
|
|
|
@@ -552,6 +561,20 @@ function resolvePluginDir(fullId, marketplaces) {
|
|
|
552
561
|
return findPlugin(fullId, marketplaces)?._pluginDir || null;
|
|
553
562
|
}
|
|
554
563
|
|
|
564
|
+
function resolveVirtualRelPath(pluginId, relPath) {
|
|
565
|
+
return pluginId === '_custom/project' && relPath.startsWith('~root/')
|
|
566
|
+
? path.join('..', relPath.slice(6))
|
|
567
|
+
: relPath;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function isPathAllowed(fullPath, pluginDir, pluginId) {
|
|
571
|
+
if (fullPath.startsWith(path.resolve(pluginDir))) return true;
|
|
572
|
+
if (pluginId === '_custom/project') {
|
|
573
|
+
return fullPath === path.join(path.resolve(pluginDir, '..'), 'CLAUDE.md');
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
555
578
|
// --- API Routes ---
|
|
556
579
|
|
|
557
580
|
app.get('/api/marketplaces', (req, res) => {
|
|
@@ -598,8 +621,8 @@ app.get('/api/plugins/:pluginId/preview/*', (req, res) => {
|
|
|
598
621
|
const pluginDir = resolvePluginDir(pluginId, marketplaces);
|
|
599
622
|
if (!pluginDir) return res.status(404).json({ error: 'Plugin not found' });
|
|
600
623
|
|
|
601
|
-
let fullPath = path.resolve(pluginDir, relPath);
|
|
602
|
-
if (!fullPath
|
|
624
|
+
let fullPath = path.resolve(pluginDir, resolveVirtualRelPath(pluginId, relPath));
|
|
625
|
+
if (!isPathAllowed(fullPath, pluginDir, pluginId)) {
|
|
603
626
|
return res.status(403).json({ error: 'Access denied' });
|
|
604
627
|
}
|
|
605
628
|
if (!fs.existsSync(fullPath) && fs.existsSync(fullPath + '.md')) fullPath += '.md';
|
|
@@ -642,10 +665,8 @@ app.post('/api/open-in-editor', (req, res) => {
|
|
|
642
665
|
if (fs.existsSync(pluginJson)) args.push(pluginJson);
|
|
643
666
|
|
|
644
667
|
if (relativePath) {
|
|
645
|
-
const fullPath = path.resolve(pluginDir, relativePath);
|
|
646
|
-
if (fullPath
|
|
647
|
-
args.push(fullPath);
|
|
648
|
-
}
|
|
668
|
+
const fullPath = path.resolve(pluginDir, resolveVirtualRelPath(pluginId, relativePath));
|
|
669
|
+
if (isPathAllowed(fullPath, pluginDir, pluginId)) args.push(fullPath);
|
|
649
670
|
}
|
|
650
671
|
|
|
651
672
|
openVSCode(args, res);
|