promptgraph-mcp 2.2.9 → 2.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/github-import.js CHANGED
@@ -62,8 +62,8 @@ function sparseClone(url, dest, subdir) {
62
62
  git(['sparse-checkout', 'init', '--cone'], dest, 'pipe');
63
63
  git(['sparse-checkout', 'set', subdir], dest, 'pipe');
64
64
 
65
- // 3. fetch + checkout (depth=1)
66
- const fetch = git(['fetch', '--depth=1', 'origin'], dest);
65
+ // 3. fetch + checkout (depth=1, skip large blobs)
66
+ const fetch = git(['fetch', '--depth=1', '--filter=blob:none', 'origin'], dest);
67
67
  if (fetch.status !== 0) return false;
68
68
 
69
69
  // Try HEAD, then main, then master
@@ -90,16 +90,17 @@ function sparseUpdate(dest, subdir) {
90
90
  }
91
91
 
92
92
  // Fallback: full clone (when no subdir found)
93
+ // --filter=blob:none skips large binaries (images, zips) — only fetches text files
93
94
  function fullClone(url, dest) {
94
95
  if (fs.existsSync(dest)) {
95
- const fetch = git(['fetch', '--depth=1', 'origin'], dest);
96
+ const fetch = git(['fetch', '--depth=1', '--filter=blob:none', 'origin'], dest);
96
97
  if (fetch.status !== 0) return false;
97
98
  for (const ref of ['origin/HEAD', 'origin/main', 'origin/master']) {
98
99
  if (git(['reset', '--hard', ref], dest, 'pipe').status === 0) return true;
99
100
  }
100
101
  return false;
101
102
  }
102
- return git(['clone', '--depth=1', url, dest]).status === 0;
103
+ return git(['clone', '--depth=1', '--filter=blob:none', url, dest]).status === 0;
103
104
  }
104
105
 
105
106
  // After clone: detect actual skills dir on disk
package/index.js CHANGED
@@ -244,6 +244,9 @@ if (args[0] === 'marketplace') {
244
244
  } catch {}
245
245
 
246
246
  const { runTUI } = await import('./tui.js');
247
+ const { loadConfig: _lcR, saveConfig: _scR, SKILLS_STORE_DIR: _ssR } = await import('./config.js');
248
+ const { getDb: _getDbR } = await import('./db.js');
249
+
247
250
  await runTUI(
248
251
  Array.isArray(skills) ? skills : [],
249
252
  Array.isArray(bundles) ? bundles : [],
@@ -259,7 +262,42 @@ if (args[0] === 'marketplace') {
259
262
  if (item.code) installedSet.add(item.code);
260
263
  }
261
264
  },
262
- installedSet
265
+ installedSet,
266
+ async (item) => {
267
+ const cfg = _lcR();
268
+ const db = _getDbR();
269
+ if (item.type === 'bundle' && item.repo_url) {
270
+ // Remove cloned directory
271
+ const owner = item.repo_url.split('/')[0];
272
+ const repo = item.repo_url.split('/')[1];
273
+ const clonedName = `${owner}-${repo}`;
274
+ const clonedDir = path.join(_ssR, 'github', clonedName);
275
+ if (fs.existsSync(clonedDir)) fs.rmSync(clonedDir, { recursive: true, force: true });
276
+ // Remove from config + DB
277
+ const src = `github:${clonedName}`;
278
+ cfg.sources = cfg.sources.filter(s => s.source !== src && !s.dir.startsWith(clonedDir));
279
+ _scR(cfg);
280
+ db.prepare('DELETE FROM skills WHERE source = ?').run(src);
281
+ db.prepare('DELETE FROM chunks WHERE skill_id NOT IN (SELECT id FROM skills)').run();
282
+ } else if (item.type === 'bundle') {
283
+ // skill-list bundle — remove each skill file
284
+ const mktDir = path.join(_ssR, 'marketplace');
285
+ for (const sid of (item.skills || [])) {
286
+ const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
287
+ if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
288
+ db.prepare('DELETE FROM skills WHERE id = ?').run(sid);
289
+ db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(sid);
290
+ }
291
+ } else {
292
+ // individual skill
293
+ const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(item.id);
294
+ if (row?.path && fs.existsSync(row.path)) fs.unlinkSync(row.path);
295
+ db.prepare('DELETE FROM skills WHERE id = ?').run(item.id);
296
+ db.prepare('DELETE FROM chunks WHERE skill_id = ?').run(item.id);
297
+ }
298
+ installedSet.delete(item.id);
299
+ if (item.code) installedSet.delete(item.code);
300
+ }
263
301
  );
264
302
  process.exit(0);
265
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.2.9",
3
+ "version": "2.3.1",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -171,17 +171,23 @@ function render(state, installedSet = new Set()) {
171
171
  // ── footer ─────────────────────────────────────────────────────────────────
172
172
  write(dim('─'.repeat(cols)) + CLEAR_EOL + '\n');
173
173
  const sel = items[cursor];
174
- if (sel && !searching) {
174
+ if (sel && !searching && !state.confirming) {
175
175
  const isInst = installedSet.has(sel.id) || (sel.code && installedSet.has(sel.code));
176
176
  const installCmd = sel.type === 'bundle' ? `bundle install ${sel.id}` : `install ${sel.code || sel.id}`;
177
- const instLabel = isInst ? green(' ✓ installed') + dim(' ') : dim(' Enter') + chalk.white(' install') + dim(' ');
177
+ const instLabel = isInst
178
+ ? green(' ✓ installed') + dim(' ') + dim('d') + chalk.red(' remove') + dim(' ')
179
+ : dim(' Enter') + chalk.white(' install') + dim(' ');
178
180
  write(instLabel + dim('Tab') + ' switch ' + dim('/') + ' search ' + dim('q') + ' quit' + CLEAR_EOL + '\n');
179
181
  write(dim(` → pg ${installCmd}`) + CLEAR_EOL + '\n');
182
+ } else if (state.confirming) {
183
+ write(chalk.red.bold(' Remove ') + chalk.white(state.confirming.name) + chalk.red('? ') +
184
+ chalk.white.bold('[y]') + chalk.gray('es ') + chalk.white.bold('[n]') + chalk.gray('o') + CLEAR_EOL + '\n');
185
+ write(CLEAR_EOL + '\n');
180
186
  } else if (searching) {
181
187
  write(dim(' Type to filter ') + cyan('Enter') + dim(' confirm ') + cyan('Esc') + dim(' cancel') + CLEAR_EOL + '\n');
182
188
  write(CLEAR_EOL + '\n');
183
189
  } else {
184
- write(dim(' ↑↓ navigate Enter install Tab switch / search q quit') + CLEAR_EOL + '\n');
190
+ write(dim(' ↑↓ navigate Enter install d remove Tab switch / search q quit') + CLEAR_EOL + '\n');
185
191
  write(CLEAR_EOL + '\n');
186
192
  }
187
193
  }
@@ -224,13 +230,14 @@ function clampScroll(state) {
224
230
 
225
231
  // ── main ─────────────────────────────────────────────────────────────────────
226
232
 
227
- export async function runTUI(allSkills, allBundles, installFn, installedSet = new Set()) {
233
+ export async function runTUI(allSkills, allBundles, installFn, installedSet = new Set(), removeFn = async () => {}) {
228
234
  const allItems = buildItems(allSkills, allBundles);
229
235
 
230
236
  const state = {
231
237
  tab: 'all',
232
238
  query: '',
233
239
  searching: false,
240
+ confirming: null, // { id, name, type, repoUrl }
234
241
  cursor: 0,
235
242
  scroll: 0,
236
243
  items: allItems,
@@ -293,6 +300,27 @@ export async function runTUI(allSkills, allBundles, installFn, installedSet = ne
293
300
  return;
294
301
  }
295
302
 
303
+ // Confirm-delete mode
304
+ if (state.confirming) {
305
+ if (ch === 'y' || ch === 'Y') {
306
+ const item = state.confirming;
307
+ state.confirming = null;
308
+ setStatus(null, `Removing ${item.name}…`);
309
+ try {
310
+ await removeFn(item);
311
+ installedSet.delete(item.id);
312
+ if (item.code) installedSet.delete(item.code);
313
+ setStatus(true, `Removed ${item.name}`);
314
+ } catch (e) {
315
+ setStatus(false, e.message.slice(0, 60));
316
+ }
317
+ } else {
318
+ state.confirming = null;
319
+ render(state, installedSet);
320
+ }
321
+ return;
322
+ }
323
+
296
324
  // Normal mode
297
325
  if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
298
326
  cleanup();
@@ -345,6 +373,16 @@ export async function runTUI(allSkills, allBundles, installFn, installedSet = ne
345
373
  if (key.name === 'home') { state.cursor = 0; state.scroll = 0; render(state, installedSet); return; }
346
374
  if (key.name === 'end') { state.cursor = state.items.length - 1; clampScroll(state); render(state, installedSet); return; }
347
375
 
376
+ if (ch === 'd' || ch === 'D') {
377
+ const sel = state.items[state.cursor];
378
+ if (!sel) return;
379
+ const isInst = installedSet.has(sel.id) || (sel.code && installedSet.has(sel.code));
380
+ if (!isInst) { setStatus(false, 'Not installed'); return; }
381
+ state.confirming = { id: sel.id, name: sel.name, type: sel.type, code: sel.code, repo_url: sel.repo_url };
382
+ render(state, installedSet);
383
+ return;
384
+ }
385
+
348
386
  if (key.name === 'return' || key.name === 'i') {
349
387
  const sel = state.items[state.cursor];
350
388
  if (!sel || state.installing) return;