promptgraph-mcp 2.9.45 → 2.9.47

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.
@@ -107,14 +107,22 @@ export default async function handler(args, bin) {
107
107
  );
108
108
  process.exit(1);
109
109
  }
110
- console.log(chalk.green(`found: ${detected.label}/`));
110
+ const { validMdCount, hasScripts } = detected;
111
+ console.log(chalk.green(`found: ${detected.label}/`) + chalk.gray(` (${validMdCount} valid skills${hasScripts ? ', has scripts 🔧' : ''})`));
112
+
113
+ if (validMdCount === 0) {
114
+ error(`Cannot publish: ${repo}/${detected.label} has no skills that pass validation.\n All .md files were filtered (README/changelog/docs/etc).`);
115
+ process.exit(1);
116
+ }
117
+
111
118
  const name = repo.split('/')[1].replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
112
119
  const id = repo.replace('/', '-').toLowerCase();
113
120
  const bundle = {
114
121
  id, name, repo_url: repo, author: repo.split('/')[0],
115
122
  description: `Skills from ${repo}`,
116
123
  tags: ['community'],
117
- stars: 0
124
+ stars: 0,
125
+ ...(hasScripts && { has_tools: true }),
118
126
  };
119
127
  const json = JSON.stringify(bundle, null, 2);
120
128
 
@@ -35,17 +35,31 @@ export default async function handler(args, bin) {
35
35
 
36
36
  info(`Current: ${chalk.gray('v' + currentVersion)} → Latest: ${chalk.white.bold('v' + latest)}`);
37
37
 
38
- // Kill other node processes that may lock native .node files (e.g. pg reindex still running)
39
- // Exclude current PID to avoid killing ourselves
38
+ // Kill other promptgraph node processes that may lock native .node files
40
39
  if (process.platform === 'win32') {
41
- spawnSync('wmic', ['process', 'where', `name='node.exe' and ProcessId!=${process.pid}`, 'delete'], { stdio: 'ignore', shell: true });
40
+ // taskkill is reliable on Windows 10/11 (wmic deprecated since Win11 22H2)
41
+ spawnSync('taskkill', ['/F', '/FI', `PID ne ${process.pid}`, '/FI', 'IMAGENAME eq node.exe'], { stdio: 'ignore', shell: true });
42
42
  } else {
43
43
  spawnSync('pkill', ['-f', 'promptgraph-mcp'], { stdio: 'ignore' });
44
44
  }
45
45
 
46
+ // Brief pause so OS releases file locks before npm touches them
47
+ await new Promise(r => setTimeout(r, 1500));
48
+
46
49
  const updateSpin = (await import('../cli.js')).spinner(`Installing promptgraph-mcp@latest (v${latest})...`);
47
50
  updateSpin.start();
48
- const result = spawnSync('npm', ['install', '-g', 'promptgraph-mcp@latest'], { encoding: 'utf8', stdio: 'pipe', shell: true });
51
+
52
+ let result = spawnSync('npm', ['install', '-g', 'promptgraph-mcp@latest'], { encoding: 'utf8', stdio: 'pipe', shell: true });
53
+
54
+ // EBUSY: MCP server may have been re-spawned by the editor — retry once with --force
55
+ if (result.status !== 0 && (result.stderr || '').includes('EBUSY')) {
56
+ updateSpin.stop();
57
+ info('File busy (MCP server still running). Close Claude Code / your editor and press Enter to retry, or Ctrl+C to cancel.');
58
+ await new Promise(r => process.stdin.once('data', r));
59
+ updateSpin.start();
60
+ result = spawnSync('npm', ['install', '-g', 'promptgraph-mcp@latest', '--force'], { encoding: 'utf8', stdio: 'pipe', shell: true });
61
+ }
62
+
49
63
  updateSpin.stop();
50
64
 
51
65
  if (result.status !== 0) {
package/github-import.js CHANGED
@@ -197,9 +197,22 @@ export async function validateRepoSkills(ownerRepo) {
197
197
  return { ok: errors.length === 0, errors, warnings };
198
198
  }
199
199
 
200
+ const SCRIPT_EXTS_API = new Set(['.py', '.sh', '.bash', '.js', '.ts', '.rb']);
201
+
202
+ function countValidMd(fileEntries) {
203
+ return fileEntries.filter(e =>
204
+ e.type === 'file' && e.name.endsWith('.md') &&
205
+ !SKIP_RE.test(e.name.replace(/\.md$/i, '').toLowerCase())
206
+ ).length;
207
+ }
208
+
209
+ function hasScriptFiles(fileEntries) {
210
+ return fileEntries.some(e => e.type === 'file' && SCRIPT_EXTS_API.has(path.extname(e.name).toLowerCase()));
211
+ }
212
+
200
213
  // Ask GitHub API which subdir to use (without cloning anything). Exported for validation.
214
+ // Returns { subdir, label, validMdCount, hasScripts } or null (repo not found / no skills).
201
215
  export
202
- // Returns { subdir, label } or null (use root).
203
216
  async function detectSkillsDirFromAPI(ownerRepo) {
204
217
  try {
205
218
  const json = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents`);
@@ -208,7 +221,14 @@ async function detectSkillsDirFromAPI(ownerRepo) {
208
221
  // 1. Known skill dir names (priority order)
209
222
  const dirMap = new Map(entries.filter(e => e.type === 'dir').map(e => [e.name.toLowerCase(), e.name]));
210
223
  for (const d of SKILL_DIRS) {
211
- if (dirMap.has(d)) return { subdir: dirMap.get(d), label: d };
224
+ if (dirMap.has(d)) {
225
+ try {
226
+ const sub = JSON.parse(await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${dirMap.get(d)}`));
227
+ const validMdCount = countValidMd(sub);
228
+ if (validMdCount === 0) continue; // dir exists but no valid skills — try next
229
+ return { subdir: dirMap.get(d), label: d, validMdCount, hasScripts: hasScriptFiles(sub) };
230
+ } catch {}
231
+ }
212
232
  }
213
233
 
214
234
  // 1.5 Nested skills dirs (e.g. .claude/skills, .claude-plugin/commands)
@@ -217,30 +237,29 @@ async function detectSkillsDirFromAPI(ownerRepo) {
217
237
  for (const d of SKILL_DIRS) {
218
238
  const nested = `${prefix}/${d}`;
219
239
  try {
220
- const sub = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${nested}`);
221
- const subEntries = JSON.parse(sub);
222
- if (subEntries.length > 0) return { subdir: nested, label: nested };
240
+ const sub = JSON.parse(await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${nested}`));
241
+ const validMdCount = countValidMd(sub);
242
+ if (validMdCount > 0) return { subdir: nested, label: nested, validMdCount, hasScripts: hasScriptFiles(sub) };
223
243
  } catch {}
224
244
  }
225
245
  }
226
246
  }
227
247
 
228
- // 2. Any subdir with 2+ .md files — pick the one with most .md files
248
+ // 2. Any subdir with valid .md files — pick the one with most
229
249
  const subdirCandidates = entries.filter(e => e.type === 'dir' && !SKIP_DIRS_API.has(e.name.toLowerCase()));
230
- let best = null, bestCount = 0;
250
+ let best = null, bestCount = 0, bestScripts = false;
231
251
  for (const dir of subdirCandidates) {
232
252
  try {
233
- const sub = await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${dir.name}`);
234
- const subEntries = JSON.parse(sub);
235
- const mdCount = subEntries.filter(e => e.type === 'file' && e.name.endsWith('.md')).length;
236
- if (mdCount >= 1 && mdCount > bestCount) { best = dir.name; bestCount = mdCount; }
253
+ const sub = JSON.parse(await httpsGet(`https://api.github.com/repos/${ownerRepo}/contents/${dir.name}`));
254
+ const mdCount = countValidMd(sub);
255
+ if (mdCount > bestCount) { best = dir.name; bestCount = mdCount; bestScripts = hasScriptFiles(sub); }
237
256
  } catch {}
238
257
  }
239
- if (best) return { subdir: best, label: best };
258
+ if (best) return { subdir: best, label: best, validMdCount: bestCount, hasScripts: bestScripts };
240
259
 
241
- // 3. Root-level .md files (no subdirectory, skills live at repo root)
242
- const rootMd = entries.filter(e => e.type === 'file' && e.name.endsWith('.md') && !SKIP_RE.test(e.name.replace(/\.md$/i, '').toLowerCase()));
243
- if (rootMd.length >= 1) return { subdir: null, label: 'root' };
260
+ // 3. Root-level valid .md files
261
+ const validMdCount = countValidMd(entries);
262
+ if (validMdCount >= 1) return { subdir: null, label: 'root', validMdCount, hasScripts: hasScriptFiles(entries) };
244
263
 
245
264
  } catch {}
246
265
  return null; // repo not found or inaccessible
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptgraph-mcp",
3
- "version": "2.9.45",
3
+ "version": "2.9.47",
4
4
  "files": [
5
5
  "*.js",
6
6
  "commands/",