claude-code-marketplace 0.3.1 → 0.4.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/package.json +1 -1
- package/public/app.js +59 -23
- package/public/style.css +25 -0
- package/server.js +24 -1
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -42,6 +42,9 @@ const ICONS = {
|
|
|
42
42
|
kebab:
|
|
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
|
+
ICONS.readme = SVG(
|
|
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
|
+
);
|
|
45
48
|
ICONS.settings = ICONS.gear;
|
|
46
49
|
ICONS.openEditor =
|
|
47
50
|
'<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>';
|
|
@@ -54,6 +57,7 @@ const COMP_LABELS = {
|
|
|
54
57
|
hooks: 'Hooks',
|
|
55
58
|
lspServers: 'LSP Servers',
|
|
56
59
|
settings: 'Settings',
|
|
60
|
+
readme: 'README',
|
|
57
61
|
};
|
|
58
62
|
|
|
59
63
|
function updateArrow(p) {
|
|
@@ -505,41 +509,52 @@ function renderDetailComponents(pluginId, comps, hasDirAccess) {
|
|
|
505
509
|
const entries = Object.entries(comps).filter(
|
|
506
510
|
([k, v]) => !k.startsWith('_') && (Array.isArray(v) ? v.length > 0 : v > 0),
|
|
507
511
|
);
|
|
508
|
-
if (!entries.length
|
|
512
|
+
if (!entries.length && !comps._readmePath)
|
|
513
|
+
return '<div style="color:var(--text-dim);font-size:12px">No components found</div>';
|
|
509
514
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
+
let readmeHtml = '';
|
|
516
|
+
if (comps._readmePath && hasDirAccess) {
|
|
517
|
+
readmeHtml = `<div class="readme-comp-item" onclick="openContentModal('${esc(pluginId)}', '${esc(comps._readmePath)}', 'readme')">
|
|
518
|
+
<span class="icon">${ICONS.readme}</span> ${esc(comps._readmePath)}
|
|
519
|
+
</div>`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return (
|
|
523
|
+
readmeHtml +
|
|
524
|
+
entries
|
|
525
|
+
.map(([type, items]) => {
|
|
526
|
+
const names = Array.isArray(items) ? items : [];
|
|
527
|
+
const count = names.length || items;
|
|
528
|
+
let html = `<div class="detail-comp-group">
|
|
515
529
|
<div class="detail-comp-header">
|
|
516
530
|
<span class="comp-icon">${ICONS[type] || ''}</span>
|
|
517
531
|
${COMP_LABELS[type] || type}
|
|
518
532
|
<span class="count">${count}</span>
|
|
519
533
|
</div>`;
|
|
520
534
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
535
|
+
if (names.length) {
|
|
536
|
+
const configFile = configFiles[type];
|
|
537
|
+
const dir = COMP_HAS_DIR.has(type) ? type : null;
|
|
538
|
+
html += '<div class="detail-comp-items">';
|
|
539
|
+
for (const name of names) {
|
|
540
|
+
const clickPath = configFile || (dir ? `${dir}/${name}` : name);
|
|
541
|
+
const cls = hasDirAccess ? '' : ' disabled';
|
|
542
|
+
const click = hasDirAccess
|
|
543
|
+
? ` onclick="openContentModal('${esc(pluginId)}', '${esc(clickPath)}', '${esc(type)}')"`
|
|
544
|
+
: '';
|
|
545
|
+
html += `<div class="detail-comp-item${cls}"${click}>
|
|
532
546
|
<span class="icon">${type === 'skills' ? ICONS.folder : ICONS.file}</span>
|
|
533
547
|
${esc(name)}
|
|
534
548
|
</div>`;
|
|
549
|
+
}
|
|
550
|
+
html += '</div>';
|
|
535
551
|
}
|
|
536
|
-
html += '</div>';
|
|
537
|
-
}
|
|
538
552
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
553
|
+
html += '</div>';
|
|
554
|
+
return html;
|
|
555
|
+
})
|
|
556
|
+
.join('')
|
|
557
|
+
);
|
|
543
558
|
}
|
|
544
559
|
|
|
545
560
|
const EXT_TO_LANG = {
|
|
@@ -611,6 +626,26 @@ async function openFolderInEditor({ pluginId, marketplaceName } = {}) {
|
|
|
611
626
|
} catch {}
|
|
612
627
|
}
|
|
613
628
|
|
|
629
|
+
async function openReadmeModal(title, fetchUrl) {
|
|
630
|
+
_contentPluginId = null;
|
|
631
|
+
document.getElementById('contentModalTitle').textContent = `${title} \u2014 README`;
|
|
632
|
+
const tree = document.getElementById('contentTree');
|
|
633
|
+
const codeEl = getContentCodeEl();
|
|
634
|
+
const pathEl = document.getElementById('contentViewerPath');
|
|
635
|
+
tree.innerHTML = '';
|
|
636
|
+
pathEl.textContent = 'README.md';
|
|
637
|
+
codeEl.innerHTML = '<span style="color:var(--text-dim)">Loading...</span>';
|
|
638
|
+
document.getElementById('contentModal').classList.add('open');
|
|
639
|
+
try {
|
|
640
|
+
const res = await fetch(fetchUrl);
|
|
641
|
+
if (!res.ok) throw new Error('Not found');
|
|
642
|
+
const data = await res.json();
|
|
643
|
+
codeEl.innerHTML = highlightSource(data.content || '', data.name || 'README.md');
|
|
644
|
+
} catch {
|
|
645
|
+
codeEl.innerHTML = '<span style="color:var(--error)">Failed to load README</span>';
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
614
649
|
async function openContentModal(pluginId, initialPath, componentType) {
|
|
615
650
|
_contentPluginId = pluginId;
|
|
616
651
|
const plugin = findPlugin(pluginId);
|
|
@@ -752,6 +787,7 @@ function showMarketplaceDetail(name) {
|
|
|
752
787
|
<span class="meta-value">${installed} installed / ${total} total</span>
|
|
753
788
|
</div>
|
|
754
789
|
</div>
|
|
790
|
+
${m.readmeFile ? `<div class="detail-section"><h4>Documentation</h4><div class="readme-comp-item" onclick="openReadmeModal('${esc(m.name)}', '/api/marketplaces/${encodeURIComponent(m.name)}/readme')">${ICONS.readme} ${esc(m.readmeFile)}</div></div>` : ''}
|
|
755
791
|
<div class="detail-section">
|
|
756
792
|
<h4>Actions</h4>
|
|
757
793
|
<div class="mkt-actions">
|
package/public/style.css
CHANGED
|
@@ -934,6 +934,31 @@ body.light .scope-toggle.local {
|
|
|
934
934
|
background: none;
|
|
935
935
|
color: var(--text-tertiary);
|
|
936
936
|
}
|
|
937
|
+
.readme-comp-item {
|
|
938
|
+
display: flex;
|
|
939
|
+
align-items: center;
|
|
940
|
+
gap: 8px;
|
|
941
|
+
padding: 6px 10px;
|
|
942
|
+
font-size: 12px;
|
|
943
|
+
color: var(--accent);
|
|
944
|
+
cursor: pointer;
|
|
945
|
+
border: 1px dashed var(--border);
|
|
946
|
+
border-radius: 6px;
|
|
947
|
+
background: var(--bg-deep);
|
|
948
|
+
transition:
|
|
949
|
+
background 0.12s,
|
|
950
|
+
border-color 0.12s;
|
|
951
|
+
margin-bottom: 8px;
|
|
952
|
+
}
|
|
953
|
+
.readme-comp-item:hover {
|
|
954
|
+
background: var(--bg-hover);
|
|
955
|
+
border-color: var(--accent);
|
|
956
|
+
}
|
|
957
|
+
.readme-comp-item svg {
|
|
958
|
+
width: 14px;
|
|
959
|
+
height: 14px;
|
|
960
|
+
opacity: 0.7;
|
|
961
|
+
}
|
|
937
962
|
|
|
938
963
|
/* === TOAST === */
|
|
939
964
|
|
package/server.js
CHANGED
|
@@ -37,6 +37,12 @@ function readJsonSafe(filePath) {
|
|
|
37
37
|
} catch { return null; }
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
function findReadmeFile(dirPath) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.readdirSync(dirPath).find(f => f.toLowerCase() === 'readme.md') || null;
|
|
43
|
+
} catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
function readJsonKey(filePath, key) {
|
|
41
47
|
const data = readJsonSafe(filePath);
|
|
42
48
|
return data ? (data[key] || {}) : {};
|
|
@@ -168,6 +174,8 @@ function loadMarketplaces() {
|
|
|
168
174
|
marketplace.version = mData.version || null;
|
|
169
175
|
marketplace.owner = mData.owner || null;
|
|
170
176
|
marketplace.description = mData.description || null;
|
|
177
|
+
const mktReadme = findReadmeFile(installLocation);
|
|
178
|
+
if (mktReadme) marketplace.readmeFile = mktReadme;
|
|
171
179
|
|
|
172
180
|
for (const pd of (mData.plugins || [])) {
|
|
173
181
|
if (!pd.name) continue;
|
|
@@ -370,6 +378,9 @@ function countComponents(pluginDir, meta = {}) {
|
|
|
370
378
|
}
|
|
371
379
|
}
|
|
372
380
|
|
|
381
|
+
const readmeFile = findReadmeFile(pluginDir);
|
|
382
|
+
if (readmeFile) result._readmePath = readmeFile;
|
|
383
|
+
|
|
373
384
|
result._configFiles = configFiles;
|
|
374
385
|
return result;
|
|
375
386
|
}
|
|
@@ -529,6 +540,18 @@ app.get('/api/marketplaces', (req, res) => {
|
|
|
529
540
|
}
|
|
530
541
|
});
|
|
531
542
|
|
|
543
|
+
app.get('/api/marketplaces/:name/readme', (req, res) => {
|
|
544
|
+
const mktData = getCachedMarketplaces();
|
|
545
|
+
const m = mktData.find(m => m.name === req.params.name);
|
|
546
|
+
if (!m?.readmeFile) return res.status(404).json({ error: 'No README found' });
|
|
547
|
+
try {
|
|
548
|
+
const content = fs.readFileSync(path.join(m.installLocation, m.readmeFile), 'utf-8');
|
|
549
|
+
res.json({ type: 'file', content, name: m.readmeFile });
|
|
550
|
+
} catch {
|
|
551
|
+
res.status(404).json({ error: 'Failed to read README' });
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
532
555
|
app.get('/api/plugins/:pluginId/components', (req, res) => {
|
|
533
556
|
res.setHeader('Cache-Control', 'no-store');
|
|
534
557
|
const pluginId = decodeURIComponent(req.params.pluginId);
|
|
@@ -645,7 +668,7 @@ app.post('/api/refresh', (req, res) => {
|
|
|
645
668
|
|
|
646
669
|
function runClaudePlugin(args) {
|
|
647
670
|
return new Promise((resolve, reject) => {
|
|
648
|
-
execFile('claude', ['plugin', ...args], { timeout: 30000, shell: true }, (err, stdout, stderr) => {
|
|
671
|
+
execFile('claude', ['plugin', ...args], { timeout: 30000, shell: true, cwd: projectPath || undefined }, (err, stdout, stderr) => {
|
|
649
672
|
if (err) return reject(new Error(stderr || err.message));
|
|
650
673
|
resolve(stdout.trim());
|
|
651
674
|
});
|