@shnitzel/plugscout 0.3.26 → 0.3.28
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/dist/interfaces/cli/index.js +19 -9
- package/dist/security/live-check.js +39 -17
- package/package.json +1 -1
|
@@ -480,7 +480,7 @@ async function handleList(args) {
|
|
|
480
480
|
if (details) {
|
|
481
481
|
console.log(renderCatalogDecisionDetails(filtered, policy, insights));
|
|
482
482
|
}
|
|
483
|
-
await promptResultBrowser(filtered.map((e) => ({ id: e.item.id, name: e.item.name })));
|
|
483
|
+
await promptResultBrowser(filtered.map((e) => ({ id: e.item.id, name: e.item.name, url: buildItemLinks(e.item)[0]?.url })));
|
|
484
484
|
}
|
|
485
485
|
async function handleShow(args) {
|
|
486
486
|
const id = readFlag(args, '--id');
|
|
@@ -560,7 +560,7 @@ async function handleSearch(args) {
|
|
|
560
560
|
name: entry.item.name
|
|
561
561
|
}))));
|
|
562
562
|
printHint('Use `show --id <catalog-id>` for full detail.');
|
|
563
|
-
await promptResultBrowser(matches.map((e) => ({ id: e.item.id, name: e.item.name })));
|
|
563
|
+
await promptResultBrowser(matches.map((e) => ({ id: e.item.id, name: e.item.name, url: buildItemLinks(e.item)[0]?.url })));
|
|
564
564
|
}
|
|
565
565
|
async function handleExplain(args) {
|
|
566
566
|
const kinds = readKinds(args);
|
|
@@ -681,12 +681,16 @@ async function handleTop(args) {
|
|
|
681
681
|
printHint('Review each suggestion before installing. Do not install blindly from rank alone.');
|
|
682
682
|
printHint(`Risk scale (lower is safer): ${formatRiskScale(policy)}`);
|
|
683
683
|
printHint('Use `show --id <catalog-id>` or `assess --id <catalog-id>` for deep inspection.');
|
|
684
|
+
const topCatalogItems = await loadCatalogItems();
|
|
685
|
+
const topCatalogMap = new Map(topCatalogItems.map((item) => [item.id, item]));
|
|
684
686
|
if (details) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
console.log(renderRecommendationDecisionDetails(safe, catalogMap, policy, insights));
|
|
687
|
+
const insights = await loadItemInsights();
|
|
688
|
+
console.log(renderRecommendationDecisionDetails(safe, topCatalogMap, policy, insights));
|
|
688
689
|
}
|
|
689
|
-
await promptResultBrowser(safe.map((e) =>
|
|
690
|
+
await promptResultBrowser(safe.map((e) => {
|
|
691
|
+
const item = topCatalogMap.get(e.id);
|
|
692
|
+
return { id: e.id, name: '', url: item ? buildItemLinks(item)[0]?.url : undefined };
|
|
693
|
+
}));
|
|
690
694
|
}
|
|
691
695
|
async function handleSync(args) {
|
|
692
696
|
const kinds = readKinds(args);
|
|
@@ -787,7 +791,12 @@ async function handleRecommend(args) {
|
|
|
787
791
|
}
|
|
788
792
|
printHint('Next: run `show --id <catalog-id>` or `install --id <catalog-id> --yes`.');
|
|
789
793
|
if (format !== 'json') {
|
|
790
|
-
|
|
794
|
+
const recCatalogItems = await loadCatalogItems();
|
|
795
|
+
const recCatalogMap = new Map(recCatalogItems.map((item) => [item.id, item]));
|
|
796
|
+
await promptResultBrowser(ranked.map((e) => {
|
|
797
|
+
const item = recCatalogMap.get(e.id);
|
|
798
|
+
return { id: e.id, name: '', url: item ? buildItemLinks(item)[0]?.url : undefined };
|
|
799
|
+
}));
|
|
791
800
|
}
|
|
792
801
|
}
|
|
793
802
|
async function handleWeb(args) {
|
|
@@ -1173,15 +1182,16 @@ async function promptResultBrowser(entries) {
|
|
|
1173
1182
|
const ENTER = '\r';
|
|
1174
1183
|
const CTRL_C = '';
|
|
1175
1184
|
const Q = 'q';
|
|
1176
|
-
//
|
|
1185
|
+
// Two-line navigator: id/name line + url line (blank if none). Always writes 2 lines so cursor math is stable.
|
|
1177
1186
|
function renderNavigator(firstRender) {
|
|
1178
1187
|
if (!firstRender) {
|
|
1179
|
-
moveCursor(process.stdout, 0, -
|
|
1188
|
+
moveCursor(process.stdout, 0, -2);
|
|
1180
1189
|
clearScreenDown(process.stdout);
|
|
1181
1190
|
}
|
|
1182
1191
|
const entry = entries[selected];
|
|
1183
1192
|
const label = entry.name ? `${entry.id} \x1b[90m${entry.name}\x1b[0m` : entry.id;
|
|
1184
1193
|
process.stdout.write(` \x1b[36m❯\x1b[0m ${label} \x1b[90m(${selected + 1}/${entries.length})\x1b[0m\n`);
|
|
1194
|
+
process.stdout.write(entry.url ? ` \x1b[90m${entry.url}\x1b[0m\n` : '\n');
|
|
1185
1195
|
}
|
|
1186
1196
|
// eslint-disable-next-line prefer-const
|
|
1187
1197
|
let running = true;
|
|
@@ -107,6 +107,9 @@ async function checkNpmRegistry(pkg) {
|
|
|
107
107
|
}
|
|
108
108
|
async function checkVscodeMarketplace(vsixId) {
|
|
109
109
|
const checkedAt = new Date().toISOString();
|
|
110
|
+
const error = (status, text) => ({
|
|
111
|
+
result: { source: 'vscode-marketplace', label: 'VS Code Marketplace', status, findings: text ? [{ severity: 'info', text }] : [], checkedAt },
|
|
112
|
+
});
|
|
110
113
|
try {
|
|
111
114
|
const res = await fetchWithTimeout('https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery', {
|
|
112
115
|
method: 'POST',
|
|
@@ -117,21 +120,28 @@ async function checkVscodeMarketplace(vsixId) {
|
|
|
117
120
|
},
|
|
118
121
|
body: JSON.stringify({
|
|
119
122
|
filters: [{ criteria: [{ filterType: 7, value: vsixId }] }],
|
|
120
|
-
|
|
123
|
+
// statistics (0x200) + assetUri (0x100) + excludeNonValidated (0x20) + versionProperties (0x10)
|
|
124
|
+
flags: 0x200 | 0x100 | 0x20 | 0x10,
|
|
121
125
|
}),
|
|
122
126
|
}, 8000);
|
|
123
|
-
if (!res.ok)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
127
|
+
if (!res.ok)
|
|
128
|
+
return error('error');
|
|
126
129
|
const data = await res.json();
|
|
127
130
|
const ext = data.results?.[0]?.extensions?.[0];
|
|
128
131
|
if (!ext) {
|
|
129
132
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
result: {
|
|
134
|
+
source: 'vscode-marketplace', label: 'VS Code Marketplace', status: 'unavailable',
|
|
135
|
+
findings: [{ severity: 'warn', text: 'Extension not found in marketplace — may have been removed' }],
|
|
136
|
+
checkedAt,
|
|
137
|
+
},
|
|
133
138
|
};
|
|
134
139
|
}
|
|
140
|
+
// Extract GitHub repository URL from version properties
|
|
141
|
+
const props = ext.versions?.[0]?.properties ?? [];
|
|
142
|
+
const repoProp = props.find(p => p.key === 'Microsoft.VisualStudio.Services.Links.Source' ||
|
|
143
|
+
p.key === 'Microsoft.VisualStudio.Services.Links.GitHub');
|
|
144
|
+
const repositoryUrl = repoProp?.value?.includes('github.com') ? repoProp.value : undefined;
|
|
135
145
|
const findings = [];
|
|
136
146
|
const verified = ext.publisher.isDomainVerified ?? false;
|
|
137
147
|
const latest = ext.versions?.[0];
|
|
@@ -147,13 +157,16 @@ async function checkVscodeMarketplace(vsixId) {
|
|
|
147
157
|
findings.push({ severity: 'info', text: `${installs.toLocaleString()} installs` });
|
|
148
158
|
}
|
|
149
159
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
result: {
|
|
161
|
+
source: 'vscode-marketplace', label: 'VS Code Marketplace',
|
|
162
|
+
status: verified ? 'clean' : 'flagged',
|
|
163
|
+
findings, checkedAt,
|
|
164
|
+
},
|
|
165
|
+
repositoryUrl,
|
|
153
166
|
};
|
|
154
167
|
}
|
|
155
168
|
catch {
|
|
156
|
-
return
|
|
169
|
+
return error('error', 'Check timed out');
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
function extractGithubOwnerRepo(url) {
|
|
@@ -258,15 +271,24 @@ export async function runLiveChecks(item, opts = {}) {
|
|
|
258
271
|
checks.push(checkOsv(npmPkg));
|
|
259
272
|
checks.push(checkNpmRegistry(npmPkg));
|
|
260
273
|
}
|
|
274
|
+
// cursor-extension: marketplace first (to extract repo URL), then github check if found
|
|
275
|
+
const cursorMarketplaceResults = [];
|
|
261
276
|
if (item.kind === 'cursor-extension') {
|
|
262
277
|
const vsixId = typeof meta.vsixId === 'string' ? meta.vsixId : null;
|
|
263
278
|
if (vsixId) {
|
|
264
|
-
|
|
279
|
+
const mkt = await checkVscodeMarketplace(vsixId);
|
|
280
|
+
cursorMarketplaceResults.push(mkt.result);
|
|
281
|
+
const ghOwnerRepo = mkt.repositoryUrl ? extractGithubOwnerRepo(mkt.repositoryUrl) : findGithubOwnerRepo(item, meta);
|
|
282
|
+
if (ghOwnerRepo) {
|
|
283
|
+
checks.push(checkGithubRepo(ghOwnerRepo));
|
|
284
|
+
}
|
|
265
285
|
}
|
|
266
286
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
287
|
+
else {
|
|
288
|
+
const ghOwnerRepo = findGithubOwnerRepo(item, meta);
|
|
289
|
+
if (ghOwnerRepo) {
|
|
290
|
+
checks.push(checkGithubRepo(ghOwnerRepo));
|
|
291
|
+
}
|
|
270
292
|
}
|
|
271
293
|
if (item.kind === 'claude-plugin' || item.kind === 'claude-connector') {
|
|
272
294
|
const installUrl = item.install.url;
|
|
@@ -274,9 +296,9 @@ export async function runLiveChecks(item, opts = {}) {
|
|
|
274
296
|
checks.push(checkUrlHealth(installUrl));
|
|
275
297
|
}
|
|
276
298
|
}
|
|
277
|
-
|
|
299
|
+
const results = [...cursorMarketplaceResults, ...(checks.length > 0 ? await Promise.all(checks) : [])];
|
|
300
|
+
if (results.length === 0)
|
|
278
301
|
return [];
|
|
279
|
-
const results = await Promise.all(checks);
|
|
280
302
|
await writeCache(item.id, results);
|
|
281
303
|
return results;
|
|
282
304
|
}
|
package/package.json
CHANGED