claude-code-marketplace 0.5.7 → 0.5.9

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/README.md CHANGED
@@ -2,7 +2,9 @@
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
- [![npm](https://img.shields.io/npm/v/claude-code-marketplace)](https://www.npmjs.com/package/claude-code-marketplace)
5
+ [![npm version](https://img.shields.io/npm/v/claude-code-marketplace)](https://www.npmjs.com/package/claude-code-marketplace)
6
+ [![license](https://img.shields.io/npm/l/claude-code-marketplace)](LICENSE)
7
+ [![npm downloads](https://img.shields.io/npm/dm/claude-code-marketplace)](https://www.npmjs.com/package/claude-code-marketplace)
6
8
 
7
9
  <p align="center">
8
10
  <img src="assets/main-dark.png" alt="Marketplace — dark theme" width="100%">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-marketplace",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Web UI for browsing and managing Claude Code marketplace plugins",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -7,6 +7,7 @@ let componentCache = {};
7
7
  const detailHistory = [];
8
8
  let focusedRowId = null;
9
9
  let _focusedRowEl = null;
10
+ let treeContainer;
10
11
 
11
12
  function matchKey(e, ...keys) {
12
13
  if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) return false;
@@ -91,11 +92,13 @@ function updateArrow(p) {
91
92
 
92
93
  // --- Init ---
93
94
  document.addEventListener('DOMContentLoaded', () => {
95
+ treeContainer = document.getElementById('treeContainer');
94
96
  document.getElementById('contentOpenEditor').innerHTML = ICONS.openEditor;
95
97
  document.getElementById('contentCopyPath').innerHTML = ICONS.copyPath;
96
98
  restoreAppState();
97
99
  loadProject();
98
100
  loadData();
101
+ initSidebarResize();
99
102
 
100
103
  let searchTimer;
101
104
  document.getElementById('searchInput').addEventListener('input', (e) => {
@@ -1168,13 +1171,13 @@ function shortenPath(p) {
1168
1171
  .replace(/^\/home\/[^/]+/i, home);
1169
1172
  }
1170
1173
 
1171
- let toastTimeout;
1172
1174
  function toast(msg, type = 'info') {
1173
- const el = document.getElementById('toast');
1175
+ const container = document.getElementById('toast');
1176
+ const el = document.createElement('div');
1177
+ el.className = `toast toast-${type}`;
1174
1178
  el.textContent = msg;
1175
- el.className = `toast ${type} show`;
1176
- clearTimeout(toastTimeout);
1177
- toastTimeout = setTimeout(() => el.classList.remove('show'), 3000);
1179
+ container.appendChild(el);
1180
+ setTimeout(() => el.remove(), 3000);
1178
1181
  }
1179
1182
 
1180
1183
  function closeModal(id) {
@@ -1218,7 +1221,19 @@ async function submitAddMarketplace() {
1218
1221
  // --- Keyboard Navigation ---
1219
1222
 
1220
1223
  function getVisibleRows() {
1221
- return [...document.querySelectorAll('#treeContainer .tree-row')].filter((r) => r.offsetParent !== null);
1224
+ return [...treeContainer.querySelectorAll('.tree-row')].filter((r) => r.offsetParent !== null);
1225
+ }
1226
+
1227
+ function scrollRowIntoView(row) {
1228
+ const rowTop = row.getBoundingClientRect().top - treeContainer.getBoundingClientRect().top + treeContainer.scrollTop;
1229
+ const rowBottom = rowTop + row.offsetHeight;
1230
+ const cTop = treeContainer.scrollTop;
1231
+ const cBottom = cTop + treeContainer.clientHeight;
1232
+ if (rowTop < cTop) {
1233
+ treeContainer.scrollTop = rowTop;
1234
+ } else if (rowBottom > cBottom) {
1235
+ treeContainer.scrollTop = rowBottom - treeContainer.clientHeight;
1236
+ }
1222
1237
  }
1223
1238
 
1224
1239
  function setFocusedRow(index, rows) {
@@ -1232,7 +1247,7 @@ function setFocusedRow(index, rows) {
1232
1247
  index = Math.max(0, Math.min(index, rows.length - 1));
1233
1248
  const row = rows[index];
1234
1249
  row.classList.add('focused');
1235
- row.scrollIntoView({ block: 'nearest' });
1250
+ scrollRowIntoView(row);
1236
1251
  focusedRowId = row.dataset.rowId;
1237
1252
  _focusedRowEl = row;
1238
1253
  }
@@ -1299,6 +1314,12 @@ function handleKeydown(e) {
1299
1314
  return;
1300
1315
  }
1301
1316
 
1317
+ if (matchKey(e, 't')) {
1318
+ e.preventDefault();
1319
+ toggleTheme();
1320
+ return;
1321
+ }
1322
+
1302
1323
  const rows = getVisibleRows();
1303
1324
  const idx = getFocusedIndex(rows);
1304
1325
 
@@ -1382,6 +1403,46 @@ if ('serviceWorker' in navigator) {
1382
1403
  navigator.serviceWorker.register('/sw.js');
1383
1404
  }
1384
1405
 
1406
+ function initSidebarResize() {
1407
+ const handle = document.getElementById('resizeHandle');
1408
+ const treePanel = document.getElementById('treePanel');
1409
+ const STORAGE_KEY = 'marketplace-sidebar-width';
1410
+
1411
+ const saved = parseInt(localStorage.getItem(STORAGE_KEY), 10);
1412
+ if (saved) document.documentElement.style.setProperty('--sidebar-w', `${saved}px`);
1413
+
1414
+ let dragging = false;
1415
+ let startX, startW, maxW, currentW;
1416
+
1417
+ handle.addEventListener('mousedown', (e) => {
1418
+ dragging = true;
1419
+ startX = e.clientX;
1420
+ startW = treePanel.getBoundingClientRect().width;
1421
+ currentW = startW;
1422
+ const detailMinW = parseInt(getComputedStyle(document.getElementById('detailPanel')).minWidth, 10);
1423
+ maxW = treePanel.parentElement.clientWidth - detailMinW - handle.offsetWidth;
1424
+ handle.classList.add('is-dragging');
1425
+ document.body.style.cursor = 'col-resize';
1426
+ document.body.style.userSelect = 'none';
1427
+ e.preventDefault();
1428
+ });
1429
+
1430
+ document.addEventListener('mousemove', (e) => {
1431
+ if (!dragging) return;
1432
+ currentW = Math.max(200, Math.min(maxW, startW + e.clientX - startX));
1433
+ document.documentElement.style.setProperty('--sidebar-w', `${currentW}px`);
1434
+ });
1435
+
1436
+ document.addEventListener('mouseup', () => {
1437
+ if (!dragging) return;
1438
+ dragging = false;
1439
+ handle.classList.remove('is-dragging');
1440
+ document.body.style.cursor = '';
1441
+ document.body.style.userSelect = '';
1442
+ localStorage.setItem(STORAGE_KEY, currentW);
1443
+ });
1444
+ }
1445
+
1385
1446
  // #region HUB_INTEGRATION
1386
1447
  (async function initHub() {
1387
1448
  const cfg = await fetch('/hub-config')
package/public/index.html CHANGED
@@ -46,24 +46,25 @@
46
46
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
47
47
  Refresh
48
48
  </button>
49
+ <button class="topbar-btn" id="themeBtn" title="Toggle theme">
50
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
51
+ </button>
49
52
  <button class="topbar-btn" id="helpBtn" title="Keyboard shortcuts (?)">
50
53
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><circle cx="12" cy="17" r="0.5" fill="currentColor"/></svg>
51
54
  <kbd style="font-size:10px">?</kbd>
52
55
  </button>
53
- <button class="topbar-btn" id="themeBtn" title="Toggle theme">
54
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
55
- </button>
56
56
  <a class="topbar-btn" href="https://github.com/NikiforovAll/claude-code-marketplace" target="_blank" rel="noopener" title="GitHub">
57
57
  <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
58
58
  </a>
59
59
  </div>
60
60
 
61
61
  <div class="main-layout">
62
- <div class="tree-panel">
62
+ <div class="tree-panel" id="treePanel">
63
63
  <div class="tree-container" id="treeContainer">
64
64
  <div class="loading">Loading marketplaces...</div>
65
65
  </div>
66
66
  </div>
67
+ <div class="resize-handle" id="resizeHandle"></div>
67
68
  <div class="detail-panel" id="detailPanel">
68
69
  <div class="detail-empty">
69
70
  <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" opacity="0.3"><path d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/></svg>
@@ -72,7 +73,7 @@
72
73
  </div>
73
74
  </div>
74
75
 
75
- <div class="toast" id="toast"></div>
76
+ <div class="toast-container" id="toast"></div>
76
77
 
77
78
  <!-- Add marketplace modal -->
78
79
  <div class="modal-overlay" id="addMarketplaceModal">
@@ -134,6 +135,7 @@
134
135
  <tr><td><kbd>S</kbd></td><td>Focus scope filter</td></tr>
135
136
  <tr><td><kbd>E</kbd></td><td>Expand / Collapse all</td></tr>
136
137
  <tr><td><kbd>R</kbd></td><td>Refresh data</td></tr>
138
+ <tr><td><kbd>T</kbd></td><td>Toggle theme</td></tr>
137
139
  <tr><td><kbd>Esc</kbd></td><td>Close panel / blur input</td></tr>
138
140
  </table>
139
141
  </div>
package/public/style.css CHANGED
@@ -279,11 +279,25 @@ body {
279
279
  /* === TREE PANEL === */
280
280
 
281
281
  .tree-panel {
282
- flex: 1;
282
+ flex: 0 0 var(--sidebar-w, 400px);
283
283
  display: flex;
284
284
  flex-direction: column;
285
285
  overflow: hidden;
286
- min-width: 400px;
286
+ min-width: 200px;
287
+ }
288
+
289
+ .resize-handle {
290
+ width: 4px;
291
+ flex-shrink: 0;
292
+ cursor: col-resize;
293
+ background: transparent;
294
+ transition: background 0.15s;
295
+ position: relative;
296
+ z-index: 1;
297
+ }
298
+ .resize-handle:hover,
299
+ .resize-handle.is-dragging {
300
+ background: var(--accent);
287
301
  }
288
302
 
289
303
  .tree-expand-toggle {
@@ -670,8 +684,8 @@ body.light .scope-toggle.local {
670
684
  /* === DETAIL PANEL === */
671
685
 
672
686
  .detail-panel {
673
- width: 500px;
674
- flex-shrink: 0;
687
+ flex: 1;
688
+ min-width: 300px;
675
689
  background: var(--bg-surface);
676
690
  border-left: 1px solid var(--border);
677
691
  display: flex;
@@ -995,37 +1009,47 @@ body.light .scope-toggle.local {
995
1009
 
996
1010
  /* === TOAST === */
997
1011
 
998
- .toast {
1012
+ .toast-container {
999
1013
  position: fixed;
1000
- bottom: 20px;
1001
- right: 20px;
1002
- padding: 10px 16px;
1014
+ bottom: 16px;
1015
+ right: 16px;
1016
+ z-index: 100;
1017
+ }
1018
+
1019
+ .toast {
1020
+ background: var(--bg-elevated);
1021
+ border: 1px solid var(--border);
1003
1022
  border-radius: 6px;
1004
- font-size: 12px;
1005
- font-weight: 500;
1006
- font-family: var(--mono);
1007
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
1008
- z-index: 1000;
1009
- opacity: 0;
1010
- transform: translateY(10px);
1011
- transition: all 300ms ease;
1012
- max-width: 400px;
1023
+ padding: 8px 14px;
1024
+ font-size: 11px;
1025
+ color: var(--text-secondary);
1026
+ animation: slideIn 0.2s ease;
1013
1027
  }
1014
- .toast.show {
1015
- opacity: 1;
1016
- transform: translateY(0);
1028
+
1029
+ .toast.toast-success {
1030
+ border-color: #2ea043;
1031
+ color: #2ea043;
1017
1032
  }
1018
- .toast.success {
1019
- background: var(--success);
1020
- color: #000;
1033
+
1034
+ .toast.toast-error {
1035
+ border-color: #f85149;
1036
+ color: #f85149;
1021
1037
  }
1022
- .toast.error {
1023
- background: var(--error);
1024
- color: #fff;
1038
+
1039
+ .toast.toast-info {
1040
+ border-color: var(--accent);
1041
+ color: var(--accent);
1025
1042
  }
1026
- .toast.info {
1027
- background: var(--accent);
1028
- color: #fff;
1043
+
1044
+ @keyframes slideIn {
1045
+ from {
1046
+ opacity: 0;
1047
+ transform: translateY(10px);
1048
+ }
1049
+ to {
1050
+ opacity: 1;
1051
+ transform: translateY(0);
1052
+ }
1029
1053
  }
1030
1054
 
1031
1055
  /* === MODAL === */
package/server.js CHANGED
@@ -427,9 +427,8 @@ const VIRTUAL_PREFIX = '_custom/';
427
427
  const SCOPE_LABELS = { user: 'User Customizations', project: 'Project Customizations' };
428
428
  const EMPTY_SCOPE = { installed: false, enabled: false, version: null, installPath: null };
429
429
 
430
- function scanCustomizations(basePath, scope) {
430
+ function rescanVirtualComponents(basePath, scope) {
431
431
  const components = countComponents(basePath);
432
-
433
432
  // Strip .md extensions from command/agent names for cleaner display
434
433
  components.commands = components.commands.map(n => n.replace(/\.md$/, ''));
435
434
  components.agents = components.agents.map(n => n.replace(/\.md$/, ''));
@@ -461,6 +460,12 @@ function scanCustomizations(basePath, scope) {
461
460
  }
462
461
  if (claudeMdFiles.length) components.claudeMd = claudeMdFiles;
463
462
 
463
+ return components;
464
+ }
465
+
466
+ function scanCustomizations(basePath, scope) {
467
+ const components = rescanVirtualComponents(basePath, scope);
468
+
464
469
  const hasAny = Object.values(components).some(v => Array.isArray(v) && v.length > 0);
465
470
  if (!hasAny) return null;
466
471
 
@@ -605,7 +610,10 @@ app.get('/api/plugins/:pluginId/components', (req, res) => {
605
610
  const plugin = findPlugin(pluginId, mktData);
606
611
 
607
612
  if (plugin?.isVirtual) {
608
- return res.json({ ...plugin.components, _pluginDir: plugin._pluginDir });
613
+ const scope = plugin.fullId.replace(VIRTUAL_PREFIX, '');
614
+ const comps = rescanVirtualComponents(plugin._pluginDir, scope);
615
+ comps._pluginDir = plugin._pluginDir;
616
+ return res.json(comps);
609
617
  }
610
618
  if (!plugin?._pluginDir) return res.status(404).json({ error: 'Plugin directory not found', pluginId });
611
619