claude-code-marketplace 0.6.0 → 0.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-marketplace",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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,11 +48,14 @@ ICONS.readme = SVG(
48
48
  );
49
49
  ICONS.settings = ICONS.gear;
50
50
  ICONS.claudeMd = ICONS.readme;
51
+ ICONS.agentsMd = ICONS.readme;
52
+ ICONS.agentSkills = ICONS.skills;
51
53
  ICONS.openEditor =
52
54
  '<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>';
53
55
  ICONS.copyPath =
54
56
  '<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>';
55
- const COMP_HAS_DIR = new Set(['skills', 'commands', 'agents']);
57
+ const COMP_DIR_MAP = { skills: 'skills', commands: 'commands', agents: 'agents', agentSkills: '~agents' };
58
+ const COMP_HAS_DIR = new Set(Object.keys(COMP_DIR_MAP));
56
59
  const COMP_LABELS = {
57
60
  skills: 'Skills',
58
61
  commands: 'Commands',
@@ -62,10 +65,13 @@ const COMP_LABELS = {
62
65
  lspServers: 'LSP Servers',
63
66
  settings: 'Settings',
64
67
  claudeMd: 'CLAUDE.md',
68
+ agentsMd: 'AGENTS.md',
69
+ agentSkills: 'Agent Skills (not-supported)',
65
70
  readme: 'README',
66
71
  };
67
72
 
68
73
  const VIRTUAL_ROOT_PREFIX = '~root/';
74
+ const VIRTUAL_AGENTS_PREFIX = '~agents/';
69
75
 
70
76
  function encodePathSegments(p) {
71
77
  return p.split('/').map(encodeURIComponent).join('/');
@@ -81,6 +87,12 @@ function claudeMdLabel(name, count) {
81
87
  return count > 1 ? 'CLAUDE.md (.claude/)' : name;
82
88
  }
83
89
 
90
+ function compItemLabel(type, name, count) {
91
+ if (type === 'claudeMd') return claudeMdLabel(name, count);
92
+ if (type === 'agentsMd' && name.startsWith(VIRTUAL_ROOT_PREFIX)) return name.slice(VIRTUAL_ROOT_PREFIX.length);
93
+ return name;
94
+ }
95
+
84
96
  function updateArrow(p) {
85
97
  if (!p.hasUpdate) return '';
86
98
  const title =
@@ -203,7 +215,12 @@ async function loadData() {
203
215
  }
204
216
 
205
217
  renderTree();
206
- if (selectedPluginId) showDetail(selectedPluginId);
218
+ if (selectedPluginId) {
219
+ showDetail(selectedPluginId);
220
+ } else {
221
+ const userCustom = marketplaces.flatMap((m) => m.plugins).find((p) => p.fullId === '_custom/user');
222
+ if (userCustom) showDetail('_custom/user');
223
+ }
207
224
 
208
225
  // Prefetch components for all plugins in background
209
226
  for (const m of marketplaces) {
@@ -603,7 +620,8 @@ function renderDetailComponents(pluginId, comps, hasDirAccess) {
603
620
  .map(([type, items]) => {
604
621
  const names = Array.isArray(items) ? items : [];
605
622
  const count = names.length || items;
606
- let html = `<div class="detail-comp-group">
623
+ const groupCls = type === 'agentSkills' || type === 'agentsMd' ? ' view-only' : '';
624
+ let html = `<div class="detail-comp-group${groupCls}">
607
625
  <div class="detail-comp-header">
608
626
  <span class="comp-icon">${ICONS[type] || ''}</span>
609
627
  ${COMP_LABELS[type] || type}
@@ -612,7 +630,7 @@ function renderDetailComponents(pluginId, comps, hasDirAccess) {
612
630
 
613
631
  if (names.length) {
614
632
  const configFile = configFiles[type];
615
- const dir = COMP_HAS_DIR.has(type) ? type : null;
633
+ const dir = COMP_DIR_MAP[type] || null;
616
634
  html += '<div class="detail-comp-items">';
617
635
  for (const name of names) {
618
636
  const clickPath = configFile || (dir ? `${dir}/${name}` : name);
@@ -620,9 +638,10 @@ function renderDetailComponents(pluginId, comps, hasDirAccess) {
620
638
  const click = hasDirAccess
621
639
  ? ` onclick="openContentModal('${escJs(pluginId)}', '${escJs(clickPath)}', '${escJs(type)}')"`
622
640
  : '';
641
+ const isFolder = type === 'skills' || type === 'agentSkills';
623
642
  html += `<div class="detail-comp-item${cls}"${click}>
624
- <span class="icon">${type === 'skills' ? ICONS.folder : ICONS.file}</span>
625
- ${esc(type === 'claudeMd' ? claudeMdLabel(name, names.length) : name)}
643
+ <span class="icon">${isFolder ? ICONS.folder : ICONS.file}</span>
644
+ ${esc(compItemLabel(type, name, names.length))}
626
645
  </div>`;
627
646
  }
628
647
  html += '</div>';
@@ -732,10 +751,12 @@ async function copyToClipboard(text, btn) {
732
751
  async function copyContentPath(event) {
733
752
  if (!_contentPluginDir) return;
734
753
  const relativePath = getContentRelativePath();
754
+ const parentDir = _contentPluginDir.replace(/\/[^/]+\/?$/, '');
735
755
  let full;
736
756
  if (relativePath.startsWith(VIRTUAL_ROOT_PREFIX)) {
737
- const parentDir = _contentPluginDir.replace(/\/[^/]+\/?$/, '');
738
757
  full = `${parentDir}/${relativePath.slice(VIRTUAL_ROOT_PREFIX.length)}`;
758
+ } else if (relativePath.startsWith(VIRTUAL_AGENTS_PREFIX)) {
759
+ full = `${parentDir}/.agents/skills/${relativePath.slice(VIRTUAL_AGENTS_PREFIX.length)}`;
739
760
  } else {
740
761
  full = relativePath ? `${_contentPluginDir}/${relativePath}` : _contentPluginDir;
741
762
  }
@@ -798,6 +819,15 @@ async function loadContentTree(pluginId, treePath, container, depth, autoSelect)
798
819
  const data = await res.json();
799
820
 
800
821
  if (data.type === 'directory') {
822
+ if (!data.entries.length) {
823
+ container.innerHTML =
824
+ '<div style="color:var(--text-dim);font-size:11px;padding:8px 12px">(empty directory)</div>';
825
+ if (autoSelect) {
826
+ const codeEl = getContentCodeEl();
827
+ codeEl.innerHTML = '<span style="color:var(--text-dim)">No files in this directory</span>';
828
+ }
829
+ return;
830
+ }
801
831
  let firstFile = null;
802
832
  let preferredFile = null;
803
833
  for (const entry of data.entries) {
@@ -843,7 +873,11 @@ async function loadContentTree(pluginId, treePath, container, depth, autoSelect)
843
873
  async function loadContentFile(pluginId, filePath) {
844
874
  const codeEl = getContentCodeEl();
845
875
  const pathEl = document.getElementById('contentViewerPath');
846
- pathEl.textContent = filePath.startsWith(VIRTUAL_ROOT_PREFIX) ? filePath.slice(VIRTUAL_ROOT_PREFIX.length) : filePath;
876
+ pathEl.textContent = filePath.startsWith(VIRTUAL_ROOT_PREFIX)
877
+ ? filePath.slice(VIRTUAL_ROOT_PREFIX.length)
878
+ : filePath.startsWith(VIRTUAL_AGENTS_PREFIX)
879
+ ? `.agents/skills/${filePath.slice(VIRTUAL_AGENTS_PREFIX.length)}`
880
+ : filePath;
847
881
  pathEl.dataset.rawPath = filePath;
848
882
  codeEl.innerHTML = '<span style="color:var(--text-dim)">Loading...</span>';
849
883
 
@@ -1320,6 +1354,13 @@ function handleKeydown(e) {
1320
1354
  return;
1321
1355
  }
1322
1356
 
1357
+ if (matchKey(e, 'u') || matchKey(e, 'p')) {
1358
+ e.preventDefault();
1359
+ const scope = matchKey(e, 'u') ? 'user' : 'project';
1360
+ openCustomClaudeMd(scope);
1361
+ return;
1362
+ }
1363
+
1323
1364
  const rows = getVisibleRows();
1324
1365
  const idx = getFocusedIndex(rows);
1325
1366
 
@@ -1382,6 +1423,19 @@ function handleKeydown(e) {
1382
1423
  }
1383
1424
  }
1384
1425
 
1426
+ async function openCustomClaudeMd(scope) {
1427
+ const pluginId = `_custom/${scope}`;
1428
+ const plugin = findPlugin(pluginId);
1429
+ if (!plugin) return;
1430
+ const comps = (await fetchComponents(pluginId)) || {};
1431
+ const files = comps.claudeMd;
1432
+ if (!files?.length) return;
1433
+ // prefer the root CLAUDE.md over the .claude/ one
1434
+ const file = files.find((f) => !f.startsWith('~root/')) || files[0];
1435
+ showDetail(pluginId);
1436
+ openContentModal(pluginId, file, 'claudeMd');
1437
+ }
1438
+
1385
1439
  let _helpModalHandler = null;
1386
1440
 
1387
1441
  function showHelpModal() {
package/public/index.html CHANGED
@@ -136,6 +136,8 @@
136
136
  <tr><td><kbd>E</kbd></td><td>Expand / Collapse all</td></tr>
137
137
  <tr><td><kbd>R</kbd></td><td>Refresh data</td></tr>
138
138
  <tr><td><kbd>T</kbd></td><td>Toggle theme</td></tr>
139
+ <tr><td><kbd>U</kbd></td><td>Open user CLAUDE.md</td></tr>
140
+ <tr><td><kbd>P</kbd></td><td>Open project CLAUDE.md</td></tr>
139
141
  <tr><td><kbd>Esc</kbd></td><td>Close panel / blur input</td></tr>
140
142
  </table>
141
143
  </div>
package/public/style.css CHANGED
@@ -14,14 +14,15 @@
14
14
  --accent-text: #f0a070;
15
15
  --accent-dim: rgba(232, 111, 51, 0.22);
16
16
  --accent-glow: rgba(232, 111, 51, 0.55);
17
+ --gold: #d9b667;
17
18
  --success: #3ecf8e;
18
19
  --success-dim: rgba(62, 207, 142, 0.18);
19
20
  --warning: #f0b429;
20
21
  --warning-dim: rgba(240, 180, 41, 0.18);
21
- --error: #ef4444;
22
- --error-dim: rgba(239, 68, 68, 0.18);
23
- --team: #c4956a;
24
- --team-dim: rgba(196, 149, 106, 0.18);
22
+ --error: #ef5350;
23
+ --error-dim: rgba(239, 83, 80, 0.18);
24
+ --team: #60a5fa;
25
+ --team-dim: rgba(96, 165, 250, 0.18);
25
26
  --plan: #86a886;
26
27
  --plan-dim: rgba(134, 168, 134, 0.18);
27
28
  --mono: 'IBM Plex Mono', monospace;
@@ -37,23 +38,24 @@
37
38
 
38
39
  body.light {
39
40
  --bg-deep: #e8e6e3;
40
- --bg-surface: #f4f3f1;
41
- --bg-elevated: #dddbd8;
41
+ --bg-surface: #efede9;
42
+ --bg-elevated: #f7f4f0;
42
43
  --bg-hover: #d2d0cc;
43
- --border: #a09b94;
44
+ --border: #cfcbc4;
44
45
  --text-primary: #0a0a0a;
45
- --text-secondary: #333333;
46
- --text-tertiary: #555555;
47
- --text-muted: #777777;
46
+ --text-secondary: #444444;
47
+ --text-tertiary: #666666;
48
+ --text-muted: #888888;
48
49
  --accent-text: #b85a20;
49
50
  --accent-dim: rgba(232, 111, 51, 0.18);
50
51
  --accent-glow: rgba(232, 111, 51, 0.5);
52
+ --gold: #a8842f;
51
53
  --success: #1a8a5a;
52
54
  --success-dim: rgba(26, 138, 90, 0.15);
53
55
  --warning: #b07d0a;
54
56
  --warning-dim: rgba(176, 125, 10, 0.15);
55
- --error: #c53030;
56
- --error-dim: rgba(197, 48, 48, 0.15);
57
+ --error: #c0392b;
58
+ --error-dim: rgba(192, 57, 43, 0.15);
57
59
  --plan: #5a7a5a;
58
60
  --plan-dim: rgba(90, 122, 90, 0.15);
59
61
 
@@ -156,14 +158,14 @@ body {
156
158
  display: flex;
157
159
  align-items: center;
158
160
  gap: 6px;
159
- background: var(--bg-elevated);
161
+ background: var(--bg-deep);
160
162
  border: 1px solid var(--border);
161
163
  border-radius: 6px;
162
164
  padding: 6px 10px;
163
- transition: border-color 0.15s ease;
165
+ transition: box-shadow 0.15s ease;
164
166
  }
165
167
  .topbar-search:focus-within {
166
- border-color: var(--accent);
168
+ box-shadow: 0 0 0 2px var(--accent-dim);
167
169
  }
168
170
  .topbar-search svg {
169
171
  color: var(--text-muted);
@@ -183,7 +185,7 @@ body {
183
185
  }
184
186
 
185
187
  .topbar-select {
186
- background: var(--bg-elevated);
188
+ background: var(--bg-deep);
187
189
  border: 1px solid var(--border);
188
190
  border-radius: 6px;
189
191
  color: var(--text-secondary);
@@ -194,7 +196,7 @@ body {
194
196
  cursor: pointer;
195
197
  }
196
198
  .topbar-select:focus {
197
- border-color: var(--accent);
199
+ box-shadow: 0 0 0 2px var(--accent-dim);
198
200
  }
199
201
  .topbar-select option {
200
202
  background: var(--bg-surface);
@@ -1177,17 +1179,17 @@ body.light .scope-toggle.local {
1177
1179
  .modal-field input[type='text'] {
1178
1180
  width: 100%;
1179
1181
  padding: 8px 10px;
1180
- background: var(--bg-elevated);
1182
+ background: var(--bg-deep);
1181
1183
  border: 1px solid var(--border);
1182
1184
  border-radius: 6px;
1183
1185
  color: var(--text-primary);
1184
1186
  font-family: var(--mono);
1185
1187
  font-size: 12px;
1186
1188
  outline: none;
1187
- transition: border-color 0.15s ease;
1189
+ transition: box-shadow 0.15s ease;
1188
1190
  }
1189
1191
  .modal-field input[type='text']:focus {
1190
- border-color: var(--accent);
1192
+ box-shadow: 0 0 0 2px var(--accent-dim);
1191
1193
  }
1192
1194
  .modal-field input[type='text']::placeholder {
1193
1195
  color: var(--text-muted);
@@ -1389,3 +1391,10 @@ kbd {
1389
1391
  color: var(--text-muted);
1390
1392
  opacity: 0.6;
1391
1393
  }
1394
+
1395
+ .detail-comp-group.view-only {
1396
+ opacity: 0.55;
1397
+ }
1398
+ .detail-comp-group.view-only:hover {
1399
+ opacity: 0.85;
1400
+ }
package/server.js CHANGED
@@ -478,6 +478,21 @@ function rescanVirtualComponents(basePath, scope) {
478
478
  }
479
479
  if (claudeMdFiles.length) components.claudeMd = claudeMdFiles;
480
480
 
481
+ if (scope === 'project') {
482
+ const parentAgents = path.join(basePath, '..', 'AGENTS.md');
483
+ if (fs.existsSync(parentAgents)) components.agentsMd = ['~root/AGENTS.md'];
484
+ }
485
+
486
+ const agentSkillsDir = path.join(basePath, '..', '.agents', 'skills');
487
+ if (fs.existsSync(agentSkillsDir) && fs.statSync(agentSkillsDir).isDirectory()) {
488
+ try {
489
+ const dirs = fs.readdirSync(agentSkillsDir).filter(d => {
490
+ try { return fs.statSync(path.join(agentSkillsDir, d)).isDirectory(); } catch { return false; }
491
+ });
492
+ if (dirs.length) components.agentSkills = dirs;
493
+ } catch {}
494
+ }
495
+
481
496
  return components;
482
497
  }
483
498
 
@@ -585,6 +600,9 @@ function resolvePluginDir(fullId, marketplaces) {
585
600
  }
586
601
 
587
602
  function resolveVirtualRelPath(pluginId, relPath) {
603
+ if ((pluginId === '_custom/user' || pluginId === '_custom/project') && relPath.startsWith('~agents/')) {
604
+ return path.join('..', '.agents', 'skills', relPath.slice('~agents/'.length));
605
+ }
588
606
  return pluginId === '_custom/project' && relPath.startsWith('~root/')
589
607
  ? path.join('..', relPath.slice(6))
590
608
  : relPath;
@@ -592,8 +610,13 @@ function resolveVirtualRelPath(pluginId, relPath) {
592
610
 
593
611
  function isPathAllowed(fullPath, pluginDir, pluginId) {
594
612
  if (fullPath.startsWith(path.resolve(pluginDir))) return true;
613
+ const parent = path.resolve(pluginDir, '..');
595
614
  if (pluginId === '_custom/project') {
596
- return fullPath === path.join(path.resolve(pluginDir, '..'), 'CLAUDE.md');
615
+ if (fullPath === path.join(parent, 'CLAUDE.md') || fullPath === path.join(parent, 'AGENTS.md')) return true;
616
+ }
617
+ if (pluginId === '_custom/user' || pluginId === '_custom/project') {
618
+ const agentsRoot = path.join(parent, '.agents', 'skills');
619
+ if (fullPath === agentsRoot || fullPath.startsWith(agentsRoot + path.sep)) return true;
597
620
  }
598
621
  return false;
599
622
  }