claude-code-marketplace 0.4.0 → 0.4.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-marketplace",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
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
@@ -48,6 +48,8 @@ ICONS.readme = SVG(
48
48
  ICONS.settings = ICONS.gear;
49
49
  ICONS.openEditor =
50
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>';
51
+ ICONS.copyPath =
52
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
51
53
  const COMP_HAS_DIR = new Set(['skills', 'commands', 'agents']);
52
54
  const COMP_LABELS = {
53
55
  skills: 'Skills',
@@ -72,6 +74,7 @@ function updateArrow(p) {
72
74
  // --- Init ---
73
75
  document.addEventListener('DOMContentLoaded', () => {
74
76
  document.getElementById('contentOpenEditor').innerHTML = ICONS.openEditor;
77
+ document.getElementById('contentCopyPath').innerHTML = ICONS.copyPath;
75
78
  restoreAppState();
76
79
  loadProject();
77
80
  loadData();
@@ -447,7 +450,7 @@ async function showDetail(pluginId) {
447
450
  <div class="detail-header">
448
451
  <h3>${headerIcon} ${esc(plugin.name)} ${plugin.version ? `<span class="version">v${esc(plugin.version)}</span>` : ''}</h3>
449
452
  <div class="detail-header-actions">
450
- ${plugin._pluginDir ? `<button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({pluginId:'${esc(plugin.fullId)}'})">${ICONS.openEditor}</button>` : ''}
453
+ ${plugin._pluginDir ? `<button class="modal-action-btn" title="${esc(plugin._pluginDir)}" onclick="copyPluginPath('${escJs(plugin._pluginDir)}', event)">${ICONS.copyPath}</button><button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({pluginId:'${esc(plugin.fullId)}',event})">${ICONS.openEditor}</button>` : ''}
451
454
  <button class="detail-close" onclick="closeDetail()">\u2715</button>
452
455
  </div>
453
456
  </div>
@@ -576,6 +579,7 @@ const EXT_TO_LANG = {
576
579
  const PREFERRED_FILE = 'SKILL.MD';
577
580
  let _contentCodeEl = null;
578
581
  let _contentPluginId = null;
582
+ let _contentPluginDir = null;
579
583
 
580
584
  function highlightSource(text, fileName) {
581
585
  const ext = (fileName || '').split('.').pop().toLowerCase();
@@ -603,31 +607,72 @@ function getContentCodeEl() {
603
607
  return _contentCodeEl;
604
608
  }
605
609
 
606
- async function openInEditor() {
607
- if (!_contentPluginId) return;
608
- const relativePath = document.getElementById('contentViewerPath').textContent || '';
610
+ async function postAndFlash(endpoint, data, btn) {
609
611
  try {
610
- await fetch('/api/open-in-editor', {
612
+ await fetch(endpoint, {
611
613
  method: 'POST',
612
614
  headers: { 'Content-Type': 'application/json' },
613
- body: JSON.stringify({ pluginId: _contentPluginId, relativePath }),
615
+ body: JSON.stringify(data),
614
616
  });
617
+ if (btn) flashButton(btn);
615
618
  } catch {}
616
619
  }
617
620
 
618
- async function openFolderInEditor({ pluginId, marketplaceName } = {}) {
619
- if (!pluginId && !marketplaceName) return;
621
+ async function openInEditor(event) {
622
+ if (!_contentPluginId) return;
623
+ const relativePath = document.getElementById('contentViewerPath').textContent || '';
624
+ await postAndFlash('/api/open-in-editor', { pluginId: _contentPluginId, relativePath }, event?.currentTarget);
625
+ }
626
+
627
+ const CHECKMARK_SVG =
628
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>';
629
+
630
+ function flashButton(btn) {
631
+ if (btn._flashTimeout) clearTimeout(btn._flashTimeout);
632
+ if (!btn._flashOrig) btn._flashOrig = btn.innerHTML;
633
+ btn.innerHTML = CHECKMARK_SVG;
634
+ btn.classList.add('copy-success');
635
+ btn._flashTimeout = setTimeout(() => {
636
+ btn.innerHTML = btn._flashOrig;
637
+ btn.classList.remove('copy-success');
638
+ btn._flashOrig = null;
639
+ btn._flashTimeout = null;
640
+ }, 1000);
641
+ }
642
+
643
+ async function copyToClipboard(text, btn) {
620
644
  try {
621
- await fetch('/api/open-folder-in-editor', {
622
- method: 'POST',
623
- headers: { 'Content-Type': 'application/json' },
624
- body: JSON.stringify({ pluginId, marketplaceName }),
625
- });
626
- } catch {}
645
+ await navigator.clipboard.writeText(text);
646
+ } catch {
647
+ const ta = document.createElement('textarea');
648
+ ta.value = text;
649
+ document.body.appendChild(ta);
650
+ ta.select();
651
+ document.execCommand('copy');
652
+ document.body.removeChild(ta);
653
+ }
654
+ if (btn) flashButton(btn);
655
+ }
656
+
657
+ async function copyContentPath(event) {
658
+ if (!_contentPluginDir) return;
659
+ const relativePath = document.getElementById('contentViewerPath').textContent || '';
660
+ const full = relativePath ? `${_contentPluginDir}/${relativePath}` : _contentPluginDir;
661
+ await copyToClipboard(full, event?.currentTarget);
662
+ }
663
+
664
+ async function copyPluginPath(pluginDir, event) {
665
+ if (pluginDir) await copyToClipboard(pluginDir, event?.currentTarget);
666
+ }
667
+
668
+ async function openFolderInEditor({ pluginId, marketplaceName, event } = {}) {
669
+ if (!pluginId && !marketplaceName) return;
670
+ await postAndFlash('/api/open-folder-in-editor', { pluginId, marketplaceName }, event?.currentTarget);
627
671
  }
628
672
 
629
673
  async function openReadmeModal(title, fetchUrl) {
630
674
  _contentPluginId = null;
675
+ _contentPluginDir = null;
631
676
  document.getElementById('contentModalTitle').textContent = `${title} \u2014 README`;
632
677
  const tree = document.getElementById('contentTree');
633
678
  const codeEl = getContentCodeEl();
@@ -648,6 +693,8 @@ async function openReadmeModal(title, fetchUrl) {
648
693
 
649
694
  async function openContentModal(pluginId, initialPath, componentType) {
650
695
  _contentPluginId = pluginId;
696
+ const comps = await fetchComponents(pluginId);
697
+ _contentPluginDir = comps?._pluginDir || null;
651
698
  const plugin = findPlugin(pluginId);
652
699
  const label = COMP_LABELS[componentType] || componentType;
653
700
  document.getElementById('contentModalTitle').textContent = `${plugin?.name || pluginId} \u2014 ${label}`;
@@ -767,7 +814,7 @@ function showMarketplaceDetail(name) {
767
814
  <div class="detail-header">
768
815
  <h3>${ICONS.marketplace} ${esc(m.name)} ${m.version ? `<span class="version">v${esc(m.version)}</span>` : ''}</h3>
769
816
  <div class="detail-header-actions">
770
- ${m.installLocation ? `<button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({marketplaceName:'${esc(m.name)}'})">${ICONS.openEditor}</button>` : ''}
817
+ ${m.installLocation ? `<button class="modal-action-btn" title="${esc(m.installLocation)}" onclick="copyPluginPath('${escJs(m.installLocation)}', event)">${ICONS.copyPath}</button><button class="modal-action-btn" title="Open in VS Code" onclick="openFolderInEditor({marketplaceName:'${esc(m.name)}',event})">${ICONS.openEditor}</button>` : ''}
771
818
  <button class="detail-close" onclick="closeDetail()">\u2715</button>
772
819
  </div>
773
820
  </div>
@@ -1004,6 +1051,11 @@ function esc(str) {
1004
1051
  .replace(/'/g, '&#39;');
1005
1052
  }
1006
1053
 
1054
+ function escJs(str) {
1055
+ if (!str) return '';
1056
+ return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
1057
+ }
1058
+
1007
1059
  function shortenPath(p) {
1008
1060
  if (!p) return '';
1009
1061
  const home = '~';
package/public/index.html CHANGED
@@ -158,7 +158,8 @@
158
158
  <div class="modal-header">
159
159
  <h3 id="contentModalTitle">File Preview</h3>
160
160
  <div class="modal-header-actions">
161
- <button class="modal-action-btn" id="contentOpenEditor" title="Open in VS Code" onclick="openInEditor()"></button>
161
+ <button class="modal-action-btn" id="contentCopyPath" title="Copy path" onclick="copyContentPath(event)"></button>
162
+ <button class="modal-action-btn" id="contentOpenEditor" title="Open in VS Code" onclick="openInEditor(event)"></button>
162
163
  <button class="modal-close" onclick="closeModal('contentModal')">&#10005;</button>
163
164
  </div>
164
165
  </div>
package/public/style.css CHANGED
@@ -1042,6 +1042,21 @@ body.light .scope-toggle.local {
1042
1042
  color: var(--accent);
1043
1043
  background: var(--hover);
1044
1044
  }
1045
+ .modal-action-btn.copy-success {
1046
+ color: var(--success, #22c55e);
1047
+ animation: copy-flash 0.3s ease;
1048
+ }
1049
+ @keyframes copy-flash {
1050
+ 0% {
1051
+ transform: scale(1);
1052
+ }
1053
+ 50% {
1054
+ transform: scale(1.3);
1055
+ }
1056
+ 100% {
1057
+ transform: scale(1);
1058
+ }
1059
+ }
1045
1060
  .modal-close {
1046
1061
  background: none;
1047
1062
  border: none;