promptgraph-mcp 2.9.47 → 2.9.49
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 +26 -2
- package/commands/marketplace.js +2 -1
- package/github-import.js +44 -0
- package/marketplace.js +6 -2
- package/package.json +1 -1
- package/tui.js +2 -2
package/commands/bundle.js
CHANGED
|
@@ -107,14 +107,36 @@ export default async function handler(args, bin) {
|
|
|
107
107
|
);
|
|
108
108
|
process.exit(1);
|
|
109
109
|
}
|
|
110
|
-
const { validMdCount, hasScripts } = detected;
|
|
111
|
-
console.log(chalk.green(`found: ${detected.label}/`) + chalk.gray(` (
|
|
110
|
+
const { validMdCount, hasScripts: hasScriptsQuick } = detected;
|
|
111
|
+
console.log(chalk.green(`found: ${detected.label}/`) + chalk.gray(` (~${validMdCount} skills by name)`));
|
|
112
112
|
|
|
113
113
|
if (validMdCount === 0) {
|
|
114
114
|
error(`Cannot publish: ${repo}/${detected.label} has no skills that pass validation.\n All .md files were filtered (README/changelog/docs/etc).`);
|
|
115
115
|
process.exit(1);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Deep validation: fetch each file via raw (free) + validateSkill on content
|
|
119
|
+
const { deepValidateRepo: _deepVal } = await import('../github-import.js');
|
|
120
|
+
process.stdout.write(chalk.gray(` Validating skill content (${validMdCount} files)... `));
|
|
121
|
+
let deepResult;
|
|
122
|
+
try {
|
|
123
|
+
deepResult = await _deepVal(repo, detected.subdir, (done, total) => {
|
|
124
|
+
process.stdout.write(`\r Validating skill content: ${done}/${total}... `);
|
|
125
|
+
});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
process.stdout.write('\n');
|
|
128
|
+
error(`Validation failed: ${e.message}`); process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write('\n');
|
|
131
|
+
|
|
132
|
+
if (deepResult.passed === 0) {
|
|
133
|
+
error(`Cannot publish: all ${deepResult.total} skill files failed content validation (too short, security patterns, etc).`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hasScripts = hasScriptsQuick || deepResult.hasScripts;
|
|
138
|
+
console.log(chalk.green(` ✓ ${deepResult.passed}/${deepResult.total} skills valid${hasScripts ? ' 🔧 scripts detected' : ''}`));
|
|
139
|
+
|
|
118
140
|
const name = repo.split('/')[1].replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
119
141
|
const id = repo.replace('/', '-').toLowerCase();
|
|
120
142
|
const bundle = {
|
|
@@ -122,6 +144,8 @@ export default async function handler(args, bin) {
|
|
|
122
144
|
description: `Skills from ${repo}`,
|
|
123
145
|
tags: ['community'],
|
|
124
146
|
stars: 0,
|
|
147
|
+
skill_count: deepResult.passed,
|
|
148
|
+
validated: true,
|
|
125
149
|
...(hasScripts && { has_tools: true }),
|
|
126
150
|
};
|
|
127
151
|
const json = JSON.stringify(bundle, null, 2);
|
package/commands/marketplace.js
CHANGED
|
@@ -57,7 +57,8 @@ export default async function handler(args, bin) {
|
|
|
57
57
|
return { ...b, skillCount: realCount, has_tools: b.has_tools || hasScripts };
|
|
58
58
|
}
|
|
59
59
|
const cached = getCachedCount(b.repo_url);
|
|
60
|
-
|
|
60
|
+
const knownCount = cached ?? b.skill_count ?? null;
|
|
61
|
+
return knownCount !== null ? { ...b, skillCount: knownCount } : b;
|
|
61
62
|
});
|
|
62
63
|
refreshCountsInBackground(bundlesWithCounts);
|
|
63
64
|
|
package/github-import.js
CHANGED
|
@@ -53,6 +53,7 @@ function streamDownload(url, maxSize = MAX_DOWNLOAD_SIZE, redirects = 0) {
|
|
|
53
53
|
})
|
|
54
54
|
r.on('end', () => res(chunks.join('')))
|
|
55
55
|
})
|
|
56
|
+
req.setTimeout(30000, () => req.destroy(new Error('streamDownload timeout')))
|
|
56
57
|
req.on('error', rej)
|
|
57
58
|
})
|
|
58
59
|
}
|
|
@@ -80,6 +81,7 @@ async function httpsGet(url, redirects = 0) {
|
|
|
80
81
|
if (r.statusCode !== 200) { r.resume(); return rej(new Error(`HTTP ${r.statusCode}`)); }
|
|
81
82
|
const chunks = []; r.setEncoding('utf8'); r.on('data', c => chunks.push(c)); r.on('end', () => res(chunks.join('')));
|
|
82
83
|
});
|
|
84
|
+
req.setTimeout(30000, () => req.destroy(new Error('httpsGet timeout')));
|
|
83
85
|
req.on('error', rej);
|
|
84
86
|
});
|
|
85
87
|
}
|
|
@@ -265,6 +267,48 @@ async function detectSkillsDirFromAPI(ownerRepo) {
|
|
|
265
267
|
return null; // repo not found or inaccessible
|
|
266
268
|
}
|
|
267
269
|
|
|
270
|
+
// Deep-validate a repo bundle via 1 API call (tree) + raw file fetches (not rate-limited).
|
|
271
|
+
// Returns { passed, total, hasScripts } — passed = skills that survive validateSkill().
|
|
272
|
+
export async function deepValidateRepo(ownerRepo, subdir, onProgress) {
|
|
273
|
+
const treeJson = await httpsGet(`https://api.github.com/repos/${ownerRepo}/git/trees/HEAD?recursive=1`);
|
|
274
|
+
const tree = JSON.parse(treeJson);
|
|
275
|
+
|
|
276
|
+
const prefix = subdir ? `${subdir}/` : '';
|
|
277
|
+
const allBlobs = (tree.tree || []).filter(f => f.type === 'blob');
|
|
278
|
+
|
|
279
|
+
const mdFiles = allBlobs.filter(f =>
|
|
280
|
+
f.path.startsWith(prefix) &&
|
|
281
|
+
f.path.endsWith('.md') &&
|
|
282
|
+
!SKIP_RE.test(path.basename(f.path, '.md').toLowerCase())
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const hasScripts = allBlobs.some(f =>
|
|
286
|
+
f.path.startsWith(prefix) && SCRIPT_EXTS.has(path.extname(f.path).toLowerCase())
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const tmpDir = fs.mkdtempSync(path.join(PROMPTGRAPH_DIR, 'pg-val-'));
|
|
290
|
+
let passed = 0;
|
|
291
|
+
const total = mdFiles.length;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
for (let i = 0; i < mdFiles.length; i++) {
|
|
295
|
+
const f = mdFiles[i];
|
|
296
|
+
if (onProgress) onProgress(i + 1, total);
|
|
297
|
+
try {
|
|
298
|
+
const rawUrl = `https://raw.githubusercontent.com/${ownerRepo}/HEAD/${f.path}`;
|
|
299
|
+
const content = await streamDownload(rawUrl);
|
|
300
|
+
const tmpFile = path.join(tmpDir, `skill-${i}.md`);
|
|
301
|
+
fs.writeFileSync(tmpFile, content);
|
|
302
|
+
if (validateSkill(tmpFile).ok) passed++;
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
} finally {
|
|
306
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return { passed, total, hasScripts };
|
|
310
|
+
}
|
|
311
|
+
|
|
268
312
|
const SKIP_DIRS_API = new Set([
|
|
269
313
|
'.github', 'docs', 'doc', 'documentation', 'examples', 'example',
|
|
270
314
|
'tests', 'test', 'assets', 'images', 'img', 'media', 'static',
|
package/marketplace.js
CHANGED
|
@@ -314,12 +314,16 @@ export async function browseBundles(topK = 20) {
|
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
// 2. Not installed —
|
|
317
|
+
// 2. Not installed — validated registry count is authoritative; use cache if fresh
|
|
318
318
|
const cached = cache[b.repo_url];
|
|
319
319
|
if (cached && (now - cached.ts) < SKILL_COUNT_TTL) {
|
|
320
320
|
b.skillCount = cached.count;
|
|
321
321
|
return;
|
|
322
322
|
}
|
|
323
|
+
if (b.validated && b.skill_count) {
|
|
324
|
+
b.skillCount = b.skill_count;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
323
327
|
|
|
324
328
|
// 3. Fetch from GitHub API
|
|
325
329
|
const count = await countRepoSkills(b.repo_url);
|
|
@@ -328,7 +332,7 @@ export async function browseBundles(topK = 20) {
|
|
|
328
332
|
cache[b.repo_url] = { count, ts: now };
|
|
329
333
|
changed = true;
|
|
330
334
|
} else {
|
|
331
|
-
b.skillCount = cached?.count ?? b.
|
|
335
|
+
b.skillCount = cached?.count ?? b.skill_count ?? 0;
|
|
332
336
|
}
|
|
333
337
|
}));
|
|
334
338
|
|
package/package.json
CHANGED
package/tui.js
CHANGED
|
@@ -50,7 +50,7 @@ function buildItems(skills, bundles) {
|
|
|
50
50
|
items.push({ type: 'skill', id: s.id, name: s.name || s.id, description: s.description || '', category: s.category || 'Community', tags: s.tags || [], stars: s.stars || 0, code: s.code, created_at: s.created_at || null });
|
|
51
51
|
}
|
|
52
52
|
for (const b of bundles) {
|
|
53
|
-
items.push({ type: 'bundle', id: b.id, name: b.name || b.id, description: b.description || '', category: b.category || 'Community', tags: b.tags || [], stars: b.stars || 0, skillCount: b.skillCount, repo_url: b.repo_url, skills: b.skills, has_tools: b.has_tools || false, created_at: b.created_at || null });
|
|
53
|
+
items.push({ type: 'bundle', id: b.id, name: b.name || b.id, description: b.description || '', category: b.category || 'Community', tags: b.tags || [], stars: b.stars || 0, skillCount: b.skillCount, validated: b.validated || false, repo_url: b.repo_url, skills: b.skills, has_tools: b.has_tools || false, created_at: b.created_at || null });
|
|
54
54
|
}
|
|
55
55
|
return items;
|
|
56
56
|
}
|
|
@@ -165,7 +165,7 @@ function render(state, installedSet = new Set()) {
|
|
|
165
165
|
const toolsBadge = item.has_tools ? chalk.hex('#F59E0B')(' 🔧') : '';
|
|
166
166
|
const extra = item.type === 'bundle'
|
|
167
167
|
? item.skillCount
|
|
168
|
-
? blue(((installed ? '' : '~') + item.skillCount + ' sk').padEnd(8)) + toolsBadge
|
|
168
|
+
? blue(((installed || item.validated ? '' : '~') + item.skillCount + ' sk').padEnd(8)) + toolsBadge
|
|
169
169
|
: item.repo_url
|
|
170
170
|
? chalk.hex('#3B82F6')('↗ GitHub') + toolsBadge
|
|
171
171
|
: dim(((item.skills?.length || 0) + ' sk').padEnd(8)) + toolsBadge
|