claude-code-marketplace 0.4.0 → 0.4.2

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.2",
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();
@@ -117,6 +120,7 @@ document.addEventListener('DOMContentLoaded', () => {
117
120
  document.body.classList.add('light');
118
121
  }
119
122
  syncHljsTheme();
123
+ updateThemeColor(savedTheme !== 'dark');
120
124
 
121
125
  document.addEventListener('keydown', handleKeydown);
122
126
  });
@@ -129,6 +133,12 @@ function syncHljsTheme() {
129
133
  if (lightSheet) lightSheet.disabled = !light;
130
134
  }
131
135
 
136
+ function updateThemeColor(isLight) {
137
+ document.querySelectorAll('meta[name="theme-color"]').forEach((m) => {
138
+ m.setAttribute('content', isLight ? '#e8e6e3' : '#101114');
139
+ });
140
+ }
141
+
132
142
  function toggleTheme() {
133
143
  const isLight = document.body.classList.contains('light');
134
144
  document.body.classList.remove('light', 'dark-forced');
@@ -140,6 +150,7 @@ function toggleTheme() {
140
150
  localStorage.setItem('theme', 'light');
141
151
  }
142
152
  syncHljsTheme();
153
+ updateThemeColor(!isLight);
143
154
  }
144
155
 
145
156
  async function loadProject() {
@@ -447,7 +458,7 @@ async function showDetail(pluginId) {
447
458
  <div class="detail-header">
448
459
  <h3>${headerIcon} ${esc(plugin.name)} ${plugin.version ? `<span class="version">v${esc(plugin.version)}</span>` : ''}</h3>
449
460
  <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>` : ''}
461
+ ${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
462
  <button class="detail-close" onclick="closeDetail()">\u2715</button>
452
463
  </div>
453
464
  </div>
@@ -576,6 +587,7 @@ const EXT_TO_LANG = {
576
587
  const PREFERRED_FILE = 'SKILL.MD';
577
588
  let _contentCodeEl = null;
578
589
  let _contentPluginId = null;
590
+ let _contentPluginDir = null;
579
591
 
580
592
  function highlightSource(text, fileName) {
581
593
  const ext = (fileName || '').split('.').pop().toLowerCase();
@@ -603,31 +615,72 @@ function getContentCodeEl() {
603
615
  return _contentCodeEl;
604
616
  }
605
617
 
606
- async function openInEditor() {
607
- if (!_contentPluginId) return;
608
- const relativePath = document.getElementById('contentViewerPath').textContent || '';
618
+ async function postAndFlash(endpoint, data, btn) {
609
619
  try {
610
- await fetch('/api/open-in-editor', {
620
+ await fetch(endpoint, {
611
621
  method: 'POST',
612
622
  headers: { 'Content-Type': 'application/json' },
613
- body: JSON.stringify({ pluginId: _contentPluginId, relativePath }),
623
+ body: JSON.stringify(data),
614
624
  });
625
+ if (btn) flashButton(btn);
615
626
  } catch {}
616
627
  }
617
628
 
618
- async function openFolderInEditor({ pluginId, marketplaceName } = {}) {
619
- if (!pluginId && !marketplaceName) return;
629
+ async function openInEditor(event) {
630
+ if (!_contentPluginId) return;
631
+ const relativePath = document.getElementById('contentViewerPath').textContent || '';
632
+ await postAndFlash('/api/open-in-editor', { pluginId: _contentPluginId, relativePath }, event?.currentTarget);
633
+ }
634
+
635
+ const CHECKMARK_SVG =
636
+ '<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>';
637
+
638
+ function flashButton(btn) {
639
+ if (btn._flashTimeout) clearTimeout(btn._flashTimeout);
640
+ if (!btn._flashOrig) btn._flashOrig = btn.innerHTML;
641
+ btn.innerHTML = CHECKMARK_SVG;
642
+ btn.classList.add('copy-success');
643
+ btn._flashTimeout = setTimeout(() => {
644
+ btn.innerHTML = btn._flashOrig;
645
+ btn.classList.remove('copy-success');
646
+ btn._flashOrig = null;
647
+ btn._flashTimeout = null;
648
+ }, 1000);
649
+ }
650
+
651
+ async function copyToClipboard(text, btn) {
620
652
  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 {}
653
+ await navigator.clipboard.writeText(text);
654
+ } catch {
655
+ const ta = document.createElement('textarea');
656
+ ta.value = text;
657
+ document.body.appendChild(ta);
658
+ ta.select();
659
+ document.execCommand('copy');
660
+ document.body.removeChild(ta);
661
+ }
662
+ if (btn) flashButton(btn);
663
+ }
664
+
665
+ async function copyContentPath(event) {
666
+ if (!_contentPluginDir) return;
667
+ const relativePath = document.getElementById('contentViewerPath').textContent || '';
668
+ const full = relativePath ? `${_contentPluginDir}/${relativePath}` : _contentPluginDir;
669
+ await copyToClipboard(full, event?.currentTarget);
670
+ }
671
+
672
+ async function copyPluginPath(pluginDir, event) {
673
+ if (pluginDir) await copyToClipboard(pluginDir, event?.currentTarget);
674
+ }
675
+
676
+ async function openFolderInEditor({ pluginId, marketplaceName, event } = {}) {
677
+ if (!pluginId && !marketplaceName) return;
678
+ await postAndFlash('/api/open-folder-in-editor', { pluginId, marketplaceName }, event?.currentTarget);
627
679
  }
628
680
 
629
681
  async function openReadmeModal(title, fetchUrl) {
630
682
  _contentPluginId = null;
683
+ _contentPluginDir = null;
631
684
  document.getElementById('contentModalTitle').textContent = `${title} \u2014 README`;
632
685
  const tree = document.getElementById('contentTree');
633
686
  const codeEl = getContentCodeEl();
@@ -648,6 +701,8 @@ async function openReadmeModal(title, fetchUrl) {
648
701
 
649
702
  async function openContentModal(pluginId, initialPath, componentType) {
650
703
  _contentPluginId = pluginId;
704
+ const comps = await fetchComponents(pluginId);
705
+ _contentPluginDir = comps?._pluginDir || null;
651
706
  const plugin = findPlugin(pluginId);
652
707
  const label = COMP_LABELS[componentType] || componentType;
653
708
  document.getElementById('contentModalTitle').textContent = `${plugin?.name || pluginId} \u2014 ${label}`;
@@ -767,7 +822,7 @@ function showMarketplaceDetail(name) {
767
822
  <div class="detail-header">
768
823
  <h3>${ICONS.marketplace} ${esc(m.name)} ${m.version ? `<span class="version">v${esc(m.version)}</span>` : ''}</h3>
769
824
  <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>` : ''}
825
+ ${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
826
  <button class="detail-close" onclick="closeDetail()">\u2715</button>
772
827
  </div>
773
828
  </div>
@@ -1004,6 +1059,11 @@ function esc(str) {
1004
1059
  .replace(/'/g, '&#39;');
1005
1060
  }
1006
1061
 
1062
+ function escJs(str) {
1063
+ if (!str) return '';
1064
+ return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
1065
+ }
1066
+
1007
1067
  function shortenPath(p) {
1008
1068
  if (!p) return '';
1009
1069
  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
@@ -42,9 +42,9 @@ body.light {
42
42
  --bg-hover: #d2d0cc;
43
43
  --border: #a09b94;
44
44
  --text-primary: #0a0a0a;
45
- --text-secondary: #444444;
46
- --text-tertiary: #666666;
47
- --text-muted: #888888;
45
+ --text-secondary: #333333;
46
+ --text-tertiary: #555555;
47
+ --text-muted: #777777;
48
48
  --accent-text: #b85a20;
49
49
  --accent-dim: rgba(232, 111, 51, 0.18);
50
50
  --accent-glow: rgba(232, 111, 51, 0.5);
@@ -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;