claude-code-marketplace 0.3.0 → 0.3.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 +1 -1
- package/public/app.js +78 -27
- package/public/index.html +22 -0
- package/public/style.css +46 -0
- package/server.js +5 -2
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -91,17 +91,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
91
91
|
document.getElementById('addMarketplaceBtn').addEventListener('click', openAddMarketplace);
|
|
92
92
|
document.getElementById('helpBtn').addEventListener('click', showHelpModal);
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
function bindModalKeys(inputId, modalId, onSubmit) {
|
|
95
|
+
document.getElementById(inputId).addEventListener('keydown', (e) => {
|
|
96
|
+
if (e.key === 'Enter') {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
onSubmit();
|
|
99
|
+
}
|
|
100
|
+
if (e.key === 'Escape') {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
closeModal(modalId);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
bindModalKeys('marketplaceSource', 'addMarketplaceModal', submitAddMarketplace);
|
|
107
|
+
bindModalKeys('projectPathInput', 'projectPickerModal', submitProjectPicker);
|
|
105
108
|
|
|
106
109
|
const savedTheme = localStorage.getItem('theme');
|
|
107
110
|
if (savedTheme === 'dark') {
|
|
@@ -141,6 +144,7 @@ async function loadProject() {
|
|
|
141
144
|
const data = await res.json();
|
|
142
145
|
document.getElementById('projectPath').textContent = shortenPath(data.path);
|
|
143
146
|
document.getElementById('projectBtn').title = data.path;
|
|
147
|
+
saveRecentProject(data.path);
|
|
144
148
|
} catch {}
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -181,25 +185,68 @@ async function refresh() {
|
|
|
181
185
|
toast('Data refreshed', 'success');
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const handle = await window.showDirectoryPicker({ mode: 'read' });
|
|
190
|
-
dirPath = handle.name;
|
|
191
|
-
// showDirectoryPicker doesn't give full path — fall back to prompt with the name as hint
|
|
192
|
-
dirPath = prompt('Confirm project directory (browser cannot read full path):', dirPath);
|
|
193
|
-
} catch (e) {
|
|
194
|
-
if (e.name === 'AbortError') return;
|
|
195
|
-
}
|
|
188
|
+
function getRecentProjects() {
|
|
189
|
+
try {
|
|
190
|
+
return JSON.parse(localStorage.getItem('recentProjects') || '[]');
|
|
191
|
+
} catch {
|
|
192
|
+
return [];
|
|
196
193
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function saveRecentProject(projectPath) {
|
|
197
|
+
const recent = getRecentProjects().filter((p) => p !== projectPath);
|
|
198
|
+
recent.unshift(projectPath);
|
|
199
|
+
localStorage.setItem('recentProjects', JSON.stringify(recent.slice(0, 10)));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function removeRecentProject(projectPath, e) {
|
|
203
|
+
e.stopPropagation();
|
|
204
|
+
const recent = getRecentProjects().filter((p) => p !== projectPath);
|
|
205
|
+
localStorage.setItem('recentProjects', JSON.stringify(recent));
|
|
206
|
+
renderRecentProjects();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function renderRecentProjects() {
|
|
210
|
+
const container = document.getElementById('recentProjectsList');
|
|
211
|
+
const current = document.getElementById('projectBtn').title;
|
|
212
|
+
const recent = getRecentProjects().filter((p) => p !== current);
|
|
213
|
+
if (!recent.length) {
|
|
214
|
+
container.innerHTML = '';
|
|
215
|
+
return;
|
|
200
216
|
}
|
|
201
|
-
|
|
217
|
+
const escAttr = (s) => s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
218
|
+
const escJs = (s) => s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
219
|
+
container.innerHTML =
|
|
220
|
+
'<div class="recent-projects-label">Recent</div>' +
|
|
221
|
+
recent
|
|
222
|
+
.map(
|
|
223
|
+
(p) =>
|
|
224
|
+
`<div class="recent-project-item" onclick="selectRecentProject('${escJs(p)}')">` +
|
|
225
|
+
`<span>${escAttr(p)}</span>` +
|
|
226
|
+
`<button class="recent-project-remove" onclick="removeRecentProject('${escJs(p)}', event)" title="Remove">✕</button>` +
|
|
227
|
+
`</div>`,
|
|
228
|
+
)
|
|
229
|
+
.join('');
|
|
230
|
+
}
|
|
202
231
|
|
|
232
|
+
function selectRecentProject(projectPath) {
|
|
233
|
+
document.getElementById('projectPathInput').value = projectPath;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function changeProject() {
|
|
237
|
+
const current = document.getElementById('projectBtn').title;
|
|
238
|
+
document.getElementById('projectPathInput').value = current;
|
|
239
|
+
renderRecentProjects();
|
|
240
|
+
document.getElementById('projectPickerModal').classList.add('open');
|
|
241
|
+
setTimeout(() => document.getElementById('projectPathInput').focus(), 100);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function submitProjectPicker() {
|
|
245
|
+
const dirPath = document.getElementById('projectPathInput').value.trim();
|
|
246
|
+
if (!dirPath) return;
|
|
247
|
+
const btn = document.getElementById('projectPickerSubmit');
|
|
248
|
+
btn.disabled = true;
|
|
249
|
+
btn.textContent = 'Switching...';
|
|
203
250
|
try {
|
|
204
251
|
const res = await fetch('/api/project', {
|
|
205
252
|
method: 'PUT',
|
|
@@ -211,11 +258,15 @@ async function changeProject() {
|
|
|
211
258
|
toast(err.error, 'error');
|
|
212
259
|
return;
|
|
213
260
|
}
|
|
261
|
+
closeModal('projectPickerModal');
|
|
214
262
|
await loadProject();
|
|
215
263
|
await loadData();
|
|
216
264
|
toast('Project switched', 'success');
|
|
217
265
|
} catch (err) {
|
|
218
266
|
toast(err.message, 'error');
|
|
267
|
+
} finally {
|
|
268
|
+
btn.disabled = false;
|
|
269
|
+
btn.textContent = 'Switch';
|
|
219
270
|
}
|
|
220
271
|
}
|
|
221
272
|
|
package/public/index.html
CHANGED
|
@@ -95,6 +95,28 @@
|
|
|
95
95
|
</div>
|
|
96
96
|
</div>
|
|
97
97
|
|
|
98
|
+
<!-- Project picker modal -->
|
|
99
|
+
<div class="modal-overlay" id="projectPickerModal">
|
|
100
|
+
<div class="modal">
|
|
101
|
+
<div class="modal-header">
|
|
102
|
+
<h3>Switch Project</h3>
|
|
103
|
+
<button class="modal-close" onclick="closeModal('projectPickerModal')">✕</button>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="modal-body">
|
|
106
|
+
<div class="modal-field">
|
|
107
|
+
<label>Project directory</label>
|
|
108
|
+
<input type="text" id="projectPathInput" placeholder="/path/to/project" autocomplete="off">
|
|
109
|
+
<span class="modal-hint">Full path to the project directory</span>
|
|
110
|
+
</div>
|
|
111
|
+
<div id="recentProjectsList"></div>
|
|
112
|
+
<div class="modal-actions">
|
|
113
|
+
<button class="action-btn" onclick="closeModal('projectPickerModal')">Cancel</button>
|
|
114
|
+
<button class="action-btn primary" id="projectPickerSubmit" onclick="submitProjectPicker()">Switch</button>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
98
120
|
<!-- Help modal -->
|
|
99
121
|
<div class="modal-overlay" id="helpModal">
|
|
100
122
|
<div class="modal help-modal" onclick="event.stopPropagation()">
|
package/public/style.css
CHANGED
|
@@ -1096,6 +1096,52 @@ body.light .scope-toggle.local {
|
|
|
1096
1096
|
margin-top: 16px;
|
|
1097
1097
|
}
|
|
1098
1098
|
|
|
1099
|
+
/* === RECENT PROJECTS === */
|
|
1100
|
+
|
|
1101
|
+
.recent-projects-label {
|
|
1102
|
+
font-size: 11px;
|
|
1103
|
+
color: var(--text-muted);
|
|
1104
|
+
margin-bottom: 6px;
|
|
1105
|
+
text-transform: uppercase;
|
|
1106
|
+
letter-spacing: 0.5px;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.recent-project-item {
|
|
1110
|
+
display: flex;
|
|
1111
|
+
align-items: center;
|
|
1112
|
+
gap: 8px;
|
|
1113
|
+
padding: 6px 8px;
|
|
1114
|
+
border-radius: 4px;
|
|
1115
|
+
cursor: pointer;
|
|
1116
|
+
font-size: 13px;
|
|
1117
|
+
color: var(--text-secondary);
|
|
1118
|
+
font-family: var(--font-mono);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.recent-project-item:hover {
|
|
1122
|
+
background: var(--hover-bg);
|
|
1123
|
+
color: var(--text-primary);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.recent-project-remove {
|
|
1127
|
+
margin-left: auto;
|
|
1128
|
+
opacity: 0;
|
|
1129
|
+
background: none;
|
|
1130
|
+
border: none;
|
|
1131
|
+
color: var(--text-muted);
|
|
1132
|
+
cursor: pointer;
|
|
1133
|
+
font-size: 14px;
|
|
1134
|
+
padding: 0 4px;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.recent-project-item:hover .recent-project-remove {
|
|
1138
|
+
opacity: 1;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.recent-project-remove:hover {
|
|
1142
|
+
color: var(--danger);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1099
1145
|
/* === HELP MODAL === */
|
|
1100
1146
|
|
|
1101
1147
|
.help-modal {
|
package/server.js
CHANGED
|
@@ -552,10 +552,11 @@ app.get('/api/plugins/:pluginId/preview/*', (req, res) => {
|
|
|
552
552
|
const pluginDir = resolvePluginDir(pluginId, marketplaces);
|
|
553
553
|
if (!pluginDir) return res.status(404).json({ error: 'Plugin not found' });
|
|
554
554
|
|
|
555
|
-
|
|
555
|
+
let fullPath = path.resolve(pluginDir, relPath);
|
|
556
556
|
if (!fullPath.startsWith(path.resolve(pluginDir))) {
|
|
557
557
|
return res.status(403).json({ error: 'Access denied' });
|
|
558
558
|
}
|
|
559
|
+
if (!fs.existsSync(fullPath) && fs.existsSync(fullPath + '.md')) fullPath += '.md';
|
|
559
560
|
|
|
560
561
|
try {
|
|
561
562
|
const stat = fs.statSync(fullPath);
|
|
@@ -629,9 +630,11 @@ app.get('/api/project', (req, res) => {
|
|
|
629
630
|
app.put('/api/project', (req, res) => {
|
|
630
631
|
const newPath = req.body.path;
|
|
631
632
|
if (!newPath) return res.status(400).json({ error: 'path required' });
|
|
632
|
-
const
|
|
633
|
+
const expanded = newPath.startsWith('~') ? newPath.replace('~', os.homedir()) : newPath;
|
|
634
|
+
const resolved = path.resolve(expanded);
|
|
633
635
|
if (!fs.existsSync(resolved)) return res.status(400).json({ error: 'Directory does not exist' });
|
|
634
636
|
projectPath = resolved;
|
|
637
|
+
invalidateCache();
|
|
635
638
|
res.json({ path: projectPath });
|
|
636
639
|
});
|
|
637
640
|
|