promptgraph-mcp 2.1.6 → 2.1.8

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.
Files changed (3) hide show
  1. package/index.js +38 -1
  2. package/package.json +1 -1
  3. package/tui.js +26 -18
package/index.js CHANGED
@@ -182,6 +182,8 @@ if (args[0] === 'marketplace') {
182
182
  process.exit(1);
183
183
  }
184
184
  const { browseMarketplace, browseBundles, installSkill, installBundle } = await import('./marketplace.js');
185
+ const { loadConfig: _lcMkt } = await import('./config.js');
186
+ const { getDb: _getDbMkt } = await import('./db.js');
185
187
  const { spinner: spin2 } = await import('./cli.js');
186
188
  const sp = spin2('Fetching marketplace...');
187
189
  sp.start();
@@ -190,6 +192,37 @@ if (args[0] === 'marketplace') {
190
192
 
191
193
  if (skills?.error) { error(skills.error); process.exit(1); }
192
194
 
195
+ // Build installed set: bundle IDs from config sources + skill IDs from DB
196
+ const installedSet = new Set();
197
+ try {
198
+ const cfg = _lcMkt();
199
+ for (const s of cfg.sources) {
200
+ if (s.source.startsWith('github:')) installedSet.add(s.source.replace('github:', ''));
201
+ if (s.source === 'marketplace') installedSet.add('marketplace');
202
+ }
203
+ const db = _getDbMkt();
204
+ for (const row of db.prepare('SELECT id FROM skills').all()) installedSet.add(row.id);
205
+ // Also add bundle IDs by matching repo names
206
+ for (const b of (Array.isArray(bundles) ? bundles : [])) {
207
+ if (b.repo_url) {
208
+ const repoName = b.repo_url.split('/').join('-');
209
+ if ([...installedSet].some(s => s.toLowerCase().includes(b.id.split('-').pop()))) {
210
+ installedSet.add(b.id);
211
+ }
212
+ }
213
+ }
214
+ // match github sources to bundle ids
215
+ for (const s of cfg.sources) {
216
+ if (!s.source.startsWith('github:')) continue;
217
+ const srcName = s.source.replace('github:', '').toLowerCase();
218
+ for (const b of (Array.isArray(bundles) ? bundles : [])) {
219
+ if (srcName.toLowerCase().includes(b.id.toLowerCase()) || b.id.toLowerCase().includes(srcName.split('-')[0])) {
220
+ installedSet.add(b.id);
221
+ }
222
+ }
223
+ }
224
+ } catch {}
225
+
193
226
  const { runTUI } = await import('./tui.js');
194
227
  await runTUI(
195
228
  Array.isArray(skills) ? skills : [],
@@ -198,11 +231,15 @@ if (args[0] === 'marketplace') {
198
231
  if (item.type === 'bundle') {
199
232
  const r = await installBundle(item.id);
200
233
  if (r?.error) throw new Error(r.error);
234
+ installedSet.add(item.id);
201
235
  } else {
202
236
  const r = await installSkill(item.code || item.id);
203
237
  if (r?.error) throw new Error(r.error);
238
+ installedSet.add(item.id);
239
+ if (item.code) installedSet.add(item.code);
204
240
  }
205
- }
241
+ },
242
+ installedSet
206
243
  );
207
244
  process.exit(0);
208
245
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.1.6",
3
+ "version": "2.1.8",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -73,7 +73,7 @@ function filterItems(items, query, tab) {
73
73
 
74
74
  // ── render ────────────────────────────────────────────────────────────────────
75
75
 
76
- function render(state) {
76
+ function render(state, installedSet = new Set()) {
77
77
  const { cols, rows } = termSize();
78
78
  const HEADER_ROWS = 5;
79
79
  const FOOTER_ROWS = 3;
@@ -143,17 +143,21 @@ function render(state) {
143
143
  }
144
144
 
145
145
  // item row
146
- const arrow = selected ? cyan('') : ' ';
147
- const type = item.type === 'bundle' ? blue('⊞') : dim('·');
146
+ const isInstalled = item.type === 'bundle'
147
+ ? installedSet.has(item.id) || installedSet.has(item.id.toLowerCase())
148
+ : installedSet.has(item.id) || installedSet.has(item.code);
149
+ const arrow = selected ? cyan('▶') : ' ';
150
+ const type = item.type === 'bundle' ? blue('⊞') : dim('·');
151
+ const badge = isInstalled ? green('✓') : ' ';
148
152
  const nameStr = truncate(item.name, NAME_W);
149
153
  const namePad = nameStr.padEnd(NAME_W);
150
154
  const nameCol = selected ? white.bold(namePad) : white(namePad);
151
- const extra = item.type === 'bundle'
155
+ const extra = item.type === 'bundle'
152
156
  ? (item.skillCount ? blue((item.skillCount + ' sk').padEnd(8)) : blue('GitHub '))
153
157
  : dim((item.code || '').padEnd(8));
154
158
  const desc = dim(truncate(item.description, Math.max(10, DESC_W)));
155
159
 
156
- write(bg + ` ${arrow} ${type} ${nameCol} ${extra} ${desc}` + reset + CLEAR_EOL + '\n');
160
+ write(bg + ` ${arrow} ${type} ${badge} ${nameCol} ${extra} ${desc}` + reset + CLEAR_EOL + '\n');
157
161
  rendered++;
158
162
  }
159
163
 
@@ -167,8 +171,12 @@ function render(state) {
167
171
  write(dim('─'.repeat(cols)) + CLEAR_EOL + '\n');
168
172
  const sel = items[cursor];
169
173
  if (sel && !searching) {
174
+ const isInst = sel.type === 'bundle'
175
+ ? installedSet.has(sel.id) || installedSet.has(sel.id.toLowerCase())
176
+ : installedSet.has(sel.id) || installedSet.has(sel.code);
170
177
  const installCmd = sel.type === 'bundle' ? `bundle install ${sel.id}` : `install ${sel.code || sel.id}`;
171
- write(dim(` Enter`) + ' install ' + dim('Tab') + ' switch ' + dim('/') + ' search ' + dim('q') + ' quit' + CLEAR_EOL + '\n');
178
+ const instLabel = isInst ? green(' installed') + dim(' ') : dim(' Enter') + chalk.white(' install') + dim(' ');
179
+ write(instLabel + dim('Tab') + ' switch ' + dim('/') + ' search ' + dim('q') + ' quit' + CLEAR_EOL + '\n');
172
180
  write(dim(` → pg ${installCmd}`) + CLEAR_EOL + '\n');
173
181
  } else if (searching) {
174
182
  write(dim(' Type to filter ') + cyan('Enter') + dim(' confirm ') + cyan('Esc') + dim(' cancel') + CLEAR_EOL + '\n');
@@ -196,7 +204,7 @@ function clampScroll(state) {
196
204
 
197
205
  // ── main ─────────────────────────────────────────────────────────────────────
198
206
 
199
- export async function runTUI(allSkills, allBundles, installFn) {
207
+ export async function runTUI(allSkills, allBundles, installFn, installedSet = new Set()) {
200
208
  const allItems = buildItems(allSkills, allBundles);
201
209
 
202
210
  const state = {
@@ -214,7 +222,7 @@ export async function runTUI(allSkills, allBundles, installFn) {
214
222
  state.items = filterItems(allItems, q ?? state.query, t ?? state.tab);
215
223
  if (state.cursor >= state.items.length) state.cursor = Math.max(0, state.items.length - 1);
216
224
  clampScroll(state);
217
- render(state);
225
+ render(state, installedSet);
218
226
  }
219
227
 
220
228
  // Setup terminal
@@ -239,8 +247,8 @@ export async function runTUI(allSkills, allBundles, installFn) {
239
247
  function setStatus(ok, msg) {
240
248
  state.status = { ok, msg };
241
249
  clearTimeout(statusTimer);
242
- render(state);
243
- statusTimer = setTimeout(() => { state.status = null; render(state); }, 3000);
250
+ render(state, installedSet);
251
+ statusTimer = setTimeout(() => { state.status = null; render(state, installedSet); }, 3000);
244
252
  }
245
253
 
246
254
  // Keypress handler
@@ -273,7 +281,7 @@ export async function runTUI(allSkills, allBundles, installFn) {
273
281
 
274
282
  if (key.name === 'slash' || ch === '/') {
275
283
  state.searching = true;
276
- render(state);
284
+ render(state, installedSet);
277
285
  return;
278
286
  }
279
287
 
@@ -289,33 +297,33 @@ export async function runTUI(allSkills, allBundles, installFn) {
289
297
  if (key.name === 'up') {
290
298
  if (state.cursor > 0) state.cursor--;
291
299
  clampScroll(state);
292
- render(state);
300
+ render(state, installedSet);
293
301
  return;
294
302
  }
295
303
 
296
304
  if (key.name === 'down') {
297
305
  if (state.cursor < state.items.length - 1) state.cursor++;
298
306
  clampScroll(state);
299
- render(state);
307
+ render(state, installedSet);
300
308
  return;
301
309
  }
302
310
 
303
311
  if (key.name === 'pageup') {
304
312
  state.cursor = Math.max(0, state.cursor - 10);
305
313
  clampScroll(state);
306
- render(state);
314
+ render(state, installedSet);
307
315
  return;
308
316
  }
309
317
 
310
318
  if (key.name === 'pagedown') {
311
319
  state.cursor = Math.min(state.items.length - 1, state.cursor + 10);
312
320
  clampScroll(state);
313
- render(state);
321
+ render(state, installedSet);
314
322
  return;
315
323
  }
316
324
 
317
- if (key.name === 'home') { state.cursor = 0; state.scroll = 0; render(state); return; }
318
- if (key.name === 'end') { state.cursor = state.items.length - 1; clampScroll(state); render(state); return; }
325
+ if (key.name === 'home') { state.cursor = 0; state.scroll = 0; render(state, installedSet); return; }
326
+ if (key.name === 'end') { state.cursor = state.items.length - 1; clampScroll(state); render(state, installedSet); return; }
319
327
 
320
328
  if (key.name === 'return' || key.name === 'i') {
321
329
  const sel = state.items[state.cursor];
@@ -341,7 +349,7 @@ export async function runTUI(allSkills, allBundles, installFn) {
341
349
  });
342
350
 
343
351
  // Resize handler
344
- process.stdout.on('resize', () => { clampScroll(state); render(state); });
352
+ process.stdout.on('resize', () => { clampScroll(state); render(state, installedSet); });
345
353
 
346
354
  // Keep alive
347
355
  return new Promise(resolve => {