promptgraph-mcp 2.6.5 → 2.6.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.
@@ -31,8 +31,11 @@ export default async function handler(args, bin) {
31
31
  const { spinner: spin2 } = await import('../cli.js');
32
32
  const sp = spin2('Fetching marketplace...');
33
33
  sp.start();
34
- const [skills, bundles] = await Promise.all([browseMarketplace(1000), browseBundles(1000)]);
35
- sp.stop();
34
+ try {
35
+ var [skills, bundles] = await Promise.all([browseMarketplace(1000), browseBundles(1000)]);
36
+ } finally {
37
+ sp.stop();
38
+ }
36
39
 
37
40
  if (skills?.error) { error(skills.error); process.exit(1); }
38
41
 
package/github-import.js CHANGED
@@ -582,14 +582,13 @@ export async function importFromGitHub(repoUrl) {
582
582
 
583
583
  // Update skill count cache with real survivor count
584
584
  const realCount = globSync(`${dest}/**/*.md`).length;
585
+ const cacheKey = url.replace(/\.git$/, '');
585
586
  const cachePath = path.join(PROMPTGRAPH_DIR, 'skill-counts.json');
586
587
  try {
587
- const cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
588
- if (cache[url]) {
589
- cache[url].count = realCount;
590
- cache[url].ts = Date.now();
591
- fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
592
- }
588
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
589
+ const cache = JSON.parse(fs.readFileSync(cachePath, 'utf8') || '{}');
590
+ cache[cacheKey] = { count: realCount, ts: Date.now() };
591
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
593
592
  } catch {}
594
593
 
595
594
  const { dir: localDir, label: localLabel } = detectSkillsDirLocal(dest);
package/marketplace.js CHANGED
@@ -262,6 +262,15 @@ async function countRepoSkills(repoUrl) {
262
262
  } catch { return null; }
263
263
  }
264
264
 
265
+ // Count real .md files on disk for an installed bundle (always correct)
266
+ function localSkillCount(repoUrl) {
267
+ const repoName = repoUrl.replace(/^https?:\/\/github\.com\//, '').replace(/\.git$/, '').replace('/', '-');
268
+ const dest = path.join(SKILLS_STORE_DIR, 'github', repoName);
269
+ if (!fs.existsSync(dest)) return null;
270
+ const files = globSync(`${dest}/**/*.md`);
271
+ return files.length;
272
+ }
273
+
265
274
  export async function browseBundles(topK = 20) {
266
275
  try {
267
276
  const text = await fetchText(REGISTRY_URL);
@@ -273,20 +282,33 @@ export async function browseBundles(topK = 20) {
273
282
 
274
283
  await Promise.all(bundles.map(async b => {
275
284
  if (!b.repo_url) return;
285
+
286
+ // 1. If installed locally — count real files on disk (always correct)
287
+ const local = localSkillCount(b.repo_url);
288
+ if (local !== null) {
289
+ if (b.skillCount !== local) {
290
+ b.skillCount = local;
291
+ cache[b.repo_url] = { count: local, ts: now };
292
+ changed = true;
293
+ }
294
+ return;
295
+ }
296
+
297
+ // 2. Not installed — use cached API count if fresh
276
298
  const cached = cache[b.repo_url];
277
- // Use cached count if fresh, else fetch from API
278
299
  if (cached && (now - cached.ts) < SKILL_COUNT_TTL) {
279
300
  b.skillCount = cached.count;
301
+ return;
302
+ }
303
+
304
+ // 3. Fetch from GitHub API
305
+ const count = await countRepoSkills(b.repo_url);
306
+ if (count !== null) {
307
+ b.skillCount = count;
308
+ cache[b.repo_url] = { count, ts: now };
309
+ changed = true;
280
310
  } else {
281
- const count = await countRepoSkills(b.repo_url);
282
- if (count !== null) {
283
- b.skillCount = count;
284
- cache[b.repo_url] = { count, ts: now };
285
- changed = true;
286
- } else {
287
- // API failed — use stale cache if exists, else keep registry value as fallback
288
- b.skillCount = cached?.count ?? b.skillCount ?? 0;
289
- }
311
+ b.skillCount = cached?.count ?? b.skillCount ?? 0;
290
312
  }
291
313
  }));
292
314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.6.5",
3
+ "version": "2.6.7",
4
4
  "files": [
5
5
  "*.js",
6
6
  "commands/",
package/tui.js CHANGED
@@ -22,8 +22,10 @@ const green = chalk.green;
22
22
  const red = chalk.red;
23
23
  const blue = chalk.blue;
24
24
  const white = chalk.white;
25
+ const magenta = chalk.magenta;
25
26
 
26
27
  const CAT_ICON = { Engineering:'🛠', 'AI Tools':'🤖', Coding:'💻', Creative:'🎨', Security:'🔒', Community:'🌐' };
28
+ const SPINNER_FRAMES = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷']; // braille spinner
27
29
 
28
30
  // ── helpers ──────────────────────────────────────────────────────────────────
29
31
 
@@ -113,8 +115,11 @@ function render(state, installedSet = new Set()) {
113
115
  : dim(query ? query : 'type / to search, Tab to switch view');
114
116
  write(searchLabel + searchVal + CLEAR_EOL + '\n');
115
117
 
116
- // Row 4: separator (with optional status inline)
117
- if (status) {
118
+ // Row 4: separator (with optional status inline or spinner)
119
+ if (state.installing) {
120
+ const frame = SPINNER_FRAMES[Math.floor(Date.now() / 120) % SPINNER_FRAMES.length];
121
+ write(dim('─'.repeat(4)) + magenta(` ${frame} Installing… `) + CLEAR_EOL + '\n');
122
+ } else if (status) {
118
123
  const msg = status.ok ? green(' ✓ ' + status.msg) : red(' ✗ ' + status.msg);
119
124
  write(dim('─'.repeat(4)) + msg + CLEAR_EOL + '\n');
120
125
  } else {
@@ -388,11 +393,15 @@ export async function runTUI(allSkills, allBundles, installFn, installedSet = ne
388
393
  const sel = state.items[state.cursor];
389
394
  if (!sel || state.installing) return;
390
395
  state.installing = true;
391
- setStatus(null, `Installing ${sel.id}…`);
396
+ // Live spinner — keeps rendering during long installs
397
+ const spinInterval = setInterval(() => render(state, installedSet), 120);
398
+ render(state, installedSet);
392
399
  try {
393
400
  await installFn(sel);
401
+ clearInterval(spinInterval);
394
402
  setStatus(true, `Installed ${sel.id}`);
395
403
  } catch (e) {
404
+ clearInterval(spinInterval);
396
405
  setStatus(false, e.message.slice(0, 60));
397
406
  } finally {
398
407
  state.installing = false;