promptgraph-mcp 2.2.5 → 2.2.7

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 +21 -18
  2. package/package.json +1 -1
  3. package/tui.js +26 -5
package/index.js CHANGED
@@ -208,35 +208,38 @@ if (args[0] === 'marketplace') {
208
208
 
209
209
  if (skills?.error) { error(skills.error); process.exit(1); }
210
210
 
211
- // Build installed set: bundle IDs from config sources + skill IDs from DB
211
+ // Build installed set source of truth is the FILESYSTEM, not DB/config
212
212
  const installedSet = new Set();
213
213
  try {
214
214
  const cfg = _lcMkt();
215
215
  const db = _getDbMkt();
216
-
217
- // Collect installed skill IDs from DB
218
- const dbSkillIds = new Set(db.prepare('SELECT id FROM skills').all().map(r => r.id));
219
-
220
- // Build set of cloned repo names from config sources (exact: github:owner-repo)
221
- const githubSources = new Set(
222
- cfg.sources.filter(s => s.source.startsWith('github:')).map(s => s.source.replace('github:', '').toLowerCase())
223
- );
216
+ const { SKILLS_STORE_DIR } = await import('./config.js');
217
+ const githubDir = path.join(SKILLS_STORE_DIR, 'github');
224
218
 
225
219
  for (const b of (Array.isArray(bundles) ? bundles : [])) {
226
220
  if (b.repo_url) {
227
- // repo_url = "owner/repo" → cloned as "owner-repo" in github: source
228
- const clonedName = b.repo_url.replace('/', '-').toLowerCase();
229
- if (githubSources.has(clonedName)) installedSet.add(b.id);
221
+ // Check actual cloned directory exists and has files
222
+ const owner = b.repo_url.split('/')[0];
223
+ const repo = b.repo_url.split('/')[1];
224
+ const clonedName = `${owner}-${repo}`;
225
+ const clonedDir = path.join(githubDir, clonedName);
226
+ const dirExists = fs.existsSync(clonedDir) &&
227
+ fs.readdirSync(clonedDir).length > 0; // not empty
228
+ if (dirExists) installedSet.add(b.id);
230
229
  } else if (Array.isArray(b.skills)) {
231
- // skill-list bundle: installed if ALL skills are in DB
232
- if (b.skills.length > 0 && b.skills.every(sid => dbSkillIds.has(sid))) {
233
- installedSet.add(b.id);
234
- }
230
+ // skill-list bundle: check files exist on disk via DB path column
231
+ const allOnDisk = b.skills.every(sid => {
232
+ const row = db.prepare('SELECT path FROM skills WHERE id = ?').get(sid);
233
+ return row && fs.existsSync(row.path);
234
+ });
235
+ if (b.skills.length > 0 && allOnDisk) installedSet.add(b.id);
235
236
  }
236
237
  }
237
238
 
238
- // Individual skills
239
- for (const id of dbSkillIds) installedSet.add(id);
239
+ // Individual marketplace skills — check file exists on disk
240
+ for (const row of db.prepare('SELECT id, path FROM skills WHERE source = ?').all('marketplace')) {
241
+ if (fs.existsSync(row.path)) installedSet.add(row.id);
242
+ }
240
243
  } catch {}
241
244
 
242
245
  const { runTUI } = await import('./tui.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
package/tui.js CHANGED
@@ -153,9 +153,9 @@ function render(state, installedSet = new Set()) {
153
153
  ? item.skillCount
154
154
  ? blue((item.skillCount + ' sk').padEnd(8))
155
155
  : item.repo_url
156
- ? blue('GitHub ')
156
+ ? chalk.hex('#3B82F6')('↗ GitHub')
157
157
  : dim(((item.skills?.length || 0) + ' sk').padEnd(8))
158
- : dim((item.code || '').padEnd(8));
158
+ : chalk.hex('#A78BFA')((item.code || '').padEnd(10));
159
159
  const desc = dim(truncate(item.description, Math.max(10, DESC_W)));
160
160
 
161
161
  write(bg + ` ${arrow} ${type} ${badge} ${nameCol} ${extra} ${desc}` + reset + CLEAR_EOL + '\n');
@@ -188,16 +188,37 @@ function render(state, installedSet = new Set()) {
188
188
 
189
189
  // ── clamp scroll ─────────────────────────────────────────────────────────────
190
190
 
191
+ // Count how many screen rows items[start..end] occupy (including category headers)
192
+ function countVisibleRows(items, start, end) {
193
+ let rows = 0;
194
+ let lastCat = start > 0 ? items[start - 1]?.category : null;
195
+ for (let i = start; i < end && i < items.length; i++) {
196
+ if (items[i].category !== lastCat) { rows++; lastCat = items[i].category; }
197
+ rows++;
198
+ }
199
+ return rows;
200
+ }
201
+
191
202
  function clampScroll(state) {
192
203
  const { rows } = termSize();
193
204
  const HEADER_ROWS = 4, FOOTER_ROWS = 3;
194
205
  const LIST_ROWS = rows - HEADER_ROWS - FOOTER_ROWS;
195
206
  const { cursor, items } = state;
196
207
 
197
- // ensure cursor visible — approximate (category headers add extra rows)
198
- if (cursor < state.scroll) state.scroll = cursor;
199
- if (cursor >= state.scroll + LIST_ROWS - 2) state.scroll = cursor - LIST_ROWS + 3;
200
208
  if (state.scroll < 0) state.scroll = 0;
209
+ if (state.scroll > cursor) state.scroll = cursor;
210
+
211
+ // Check if cursor is visible from current scroll
212
+ const visibleRows = countVisibleRows(items, state.scroll, cursor + 1);
213
+ if (visibleRows > LIST_ROWS - 1) {
214
+ // Cursor is below visible area — scroll forward until it fits
215
+ while (state.scroll < cursor) {
216
+ state.scroll++;
217
+ const v = countVisibleRows(items, state.scroll, cursor + 1);
218
+ if (v <= LIST_ROWS - 1) break;
219
+ }
220
+ }
221
+
201
222
  if (state.scroll >= items.length) state.scroll = Math.max(0, items.length - 1);
202
223
  }
203
224