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.
- package/commands/bundle.js +10 -2
- package/commands/update.js +18 -4
- package/github-import.js +34 -15
- package/package.json +1 -1
package/commands/bundle.js
CHANGED
|
@@ -107,14 +107,22 @@ export default async function handler(args, bin) {
|
|
|
107
107
|
);
|
|
108
108
|
process.exit(1);
|
|
109
109
|
}
|
|
110
|
-
|
|
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
|
|
package/commands/update.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
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
|
|
222
|
-
if (
|
|
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
|
|
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
|
|
235
|
-
|
|
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
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
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
|