@shnitzel/plugscout 0.3.15 → 0.3.17
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/config/registries.json +985 -294
- package/dist/interfaces/cli/index.js +109 -10
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { moveCursor, clearScreenDown } from 'node:readline';
|
|
4
5
|
import { spawn } from 'node:child_process';
|
|
5
6
|
import { stdin, stdout } from 'node:process';
|
|
6
7
|
import { loadItemInsights, loadRegistries, loadSecurityPolicy } from '../../config/runtime.js';
|
|
@@ -478,6 +479,7 @@ async function handleList(args) {
|
|
|
478
479
|
if (details) {
|
|
479
480
|
console.log(renderCatalogDecisionDetails(filtered, policy, insights));
|
|
480
481
|
}
|
|
482
|
+
await promptResultBrowser(filtered.map((e) => ({ id: e.item.id, name: e.item.name })));
|
|
481
483
|
}
|
|
482
484
|
async function handleShow(args) {
|
|
483
485
|
const id = readFlag(args, '--id');
|
|
@@ -549,6 +551,7 @@ async function handleSearch(args) {
|
|
|
549
551
|
name: entry.item.name
|
|
550
552
|
}))));
|
|
551
553
|
printHint('Use `show --id <catalog-id>` for full detail.');
|
|
554
|
+
await promptResultBrowser(matches.map((e) => ({ id: e.item.id, name: e.item.name })));
|
|
552
555
|
}
|
|
553
556
|
async function handleExplain(args) {
|
|
554
557
|
const kinds = readKinds(args);
|
|
@@ -616,14 +619,18 @@ async function handleScan(args) {
|
|
|
616
619
|
printJson(payload);
|
|
617
620
|
return;
|
|
618
621
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
console.log(`
|
|
623
|
-
console.log(
|
|
624
|
-
console.log(`
|
|
622
|
+
const resolvedProject = path.resolve(project);
|
|
623
|
+
const relProject = path.relative(process.cwd(), resolvedProject) || '.';
|
|
624
|
+
const confidenceLabel = scan.inferenceConfidence >= 80 ? 'high' : scan.inferenceConfidence >= 50 ? 'medium' : 'low';
|
|
625
|
+
console.log(`Scanned: ${relProject} (${resolvedProject})`);
|
|
626
|
+
console.log('');
|
|
627
|
+
console.log(` Archetype: ${scan.inferredArchetype} (confidence ${scan.inferenceConfidence}% — ${confidenceLabel})`);
|
|
628
|
+
console.log(` Stack: ${scan.stack.join(', ') || 'none detected'}`);
|
|
629
|
+
console.log(` Capabilities: ${scan.inferredCapabilities.join(', ') || 'none inferred'}`);
|
|
630
|
+
console.log(` Tags: ${scan.compatibilityTags.join(', ') || 'none'}`);
|
|
625
631
|
console.log('');
|
|
626
632
|
if (scan.archetypeScores.length > 0) {
|
|
633
|
+
console.log('Archetype match scores (how well your project fits each pattern):');
|
|
627
634
|
console.log(renderTable([
|
|
628
635
|
{ key: 'name', header: 'ARCHETYPE', width: 36 },
|
|
629
636
|
{ key: 'score', header: 'SCORE', width: 8 }
|
|
@@ -634,13 +641,15 @@ async function handleScan(args) {
|
|
|
634
641
|
console.log('');
|
|
635
642
|
}
|
|
636
643
|
if (scan.scanEvidence.length > 0) {
|
|
637
|
-
console.log(
|
|
638
|
-
scan.scanEvidence.slice(0, 16).forEach((line) => console.log(
|
|
644
|
+
console.log(`Evidence — ${scan.scanEvidence.length} signal(s) found in ${relProject}:`);
|
|
645
|
+
scan.scanEvidence.slice(0, 16).forEach((line) => console.log(` - ${line}`));
|
|
639
646
|
if (scan.scanEvidence.length > 16) {
|
|
640
|
-
console.log(
|
|
647
|
+
console.log(` - ...and ${scan.scanEvidence.length - 16} more`);
|
|
641
648
|
}
|
|
649
|
+
console.log('');
|
|
642
650
|
}
|
|
643
|
-
printHint(
|
|
651
|
+
printHint(`Next: plugscout recommend --project ${relProject} --only-safe --limit 10`);
|
|
652
|
+
printHint('Add --explain-scan to see how these signals shape recommendations.');
|
|
644
653
|
}
|
|
645
654
|
async function handleTop(args) {
|
|
646
655
|
const project = readFlag(args, '--project') ?? '.';
|
|
@@ -668,6 +677,7 @@ async function handleTop(args) {
|
|
|
668
677
|
const catalogMap = new Map(catalogItems.map((item) => [item.id, item]));
|
|
669
678
|
console.log(renderRecommendationDecisionDetails(safe, catalogMap, policy, insights));
|
|
670
679
|
}
|
|
680
|
+
await promptResultBrowser(safe.map((e) => ({ id: e.id, name: '' })));
|
|
671
681
|
}
|
|
672
682
|
async function handleSync(args) {
|
|
673
683
|
const kinds = readKinds(args);
|
|
@@ -767,6 +777,9 @@ async function handleRecommend(args) {
|
|
|
767
777
|
console.log(`Exported ${ranked.length} recommendations to ${exportPath}`);
|
|
768
778
|
}
|
|
769
779
|
printHint('Next: run `show --id <catalog-id>` or `install --id <catalog-id> --yes`.');
|
|
780
|
+
if (format !== 'json') {
|
|
781
|
+
await promptResultBrowser(ranked.map((e) => ({ id: e.id, name: '' })));
|
|
782
|
+
}
|
|
770
783
|
}
|
|
771
784
|
async function handleWeb(args) {
|
|
772
785
|
const out = readFlag(args, '--out') ?? '.plugscout/report.html';
|
|
@@ -1127,6 +1140,92 @@ function describeRiskPosture(posture) {
|
|
|
1127
1140
|
}
|
|
1128
1141
|
return 'show full catalog/recommendation set, including blocked items with flags.';
|
|
1129
1142
|
}
|
|
1143
|
+
async function promptResultBrowser(entries) {
|
|
1144
|
+
if (!process.stdout.isTTY || entries.length === 0)
|
|
1145
|
+
return;
|
|
1146
|
+
const WINDOW = 8;
|
|
1147
|
+
let selected = 0;
|
|
1148
|
+
let linesDrawn = 0;
|
|
1149
|
+
const ARROW_UP = '[A';
|
|
1150
|
+
const ARROW_DOWN = '[B';
|
|
1151
|
+
const ENTER = '\r';
|
|
1152
|
+
const CTRL_C = '';
|
|
1153
|
+
const Q = 'q';
|
|
1154
|
+
const ESC = '\x1b';
|
|
1155
|
+
function visibleStart() {
|
|
1156
|
+
return Math.max(0, Math.min(selected - Math.floor(WINDOW / 2), entries.length - WINDOW));
|
|
1157
|
+
}
|
|
1158
|
+
function renderBrowser(firstRender) {
|
|
1159
|
+
if (!firstRender) {
|
|
1160
|
+
moveCursor(process.stdout, 0, -linesDrawn);
|
|
1161
|
+
clearScreenDown(process.stdout);
|
|
1162
|
+
}
|
|
1163
|
+
let drawn = 0;
|
|
1164
|
+
const start = visibleStart();
|
|
1165
|
+
const end = Math.min(start + WINDOW, entries.length);
|
|
1166
|
+
for (let i = start; i < end; i++) {
|
|
1167
|
+
const prefix = i === selected ? ' ❯ ' : ' ';
|
|
1168
|
+
const nameSuffix = entries[i].name ? ` ${entries[i].name}` : '';
|
|
1169
|
+
const line = `${prefix}${entries[i].id}${nameSuffix}`;
|
|
1170
|
+
process.stdout.write(`${line}\n`);
|
|
1171
|
+
drawn += 1;
|
|
1172
|
+
}
|
|
1173
|
+
if (entries.length > WINDOW) {
|
|
1174
|
+
process.stdout.write(`\x1b[2m (${selected + 1}/${entries.length})\x1b[0m\n`);
|
|
1175
|
+
drawn += 1;
|
|
1176
|
+
}
|
|
1177
|
+
linesDrawn = drawn;
|
|
1178
|
+
}
|
|
1179
|
+
// eslint-disable-next-line prefer-const
|
|
1180
|
+
let running = true;
|
|
1181
|
+
let firstLoop = true;
|
|
1182
|
+
while (running) {
|
|
1183
|
+
if (firstLoop) {
|
|
1184
|
+
process.stdout.write('\x1b[2m Inspect results: ↑↓ navigate ⏎ inspect q skip\x1b[0m\n\n');
|
|
1185
|
+
firstLoop = false;
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
process.stdout.write('\n\x1b[2m Inspect another: ↑↓ navigate ⏎ inspect q skip\x1b[0m\n\n');
|
|
1189
|
+
}
|
|
1190
|
+
linesDrawn = 0;
|
|
1191
|
+
process.stdin.setRawMode(true);
|
|
1192
|
+
process.stdin.resume();
|
|
1193
|
+
process.stdin.setEncoding('utf8');
|
|
1194
|
+
renderBrowser(true);
|
|
1195
|
+
const action = await new Promise((resolve) => {
|
|
1196
|
+
process.stdin.on('data', function onKey(key) {
|
|
1197
|
+
if (key === CTRL_C || key === Q || key === ESC) {
|
|
1198
|
+
process.stdin.removeListener('data', onKey);
|
|
1199
|
+
process.stdin.setRawMode(false);
|
|
1200
|
+
process.stdin.pause();
|
|
1201
|
+
process.stdout.write('\n');
|
|
1202
|
+
resolve({ exit: true });
|
|
1203
|
+
}
|
|
1204
|
+
else if (key === ARROW_UP) {
|
|
1205
|
+
selected = (selected - 1 + entries.length) % entries.length;
|
|
1206
|
+
renderBrowser(false);
|
|
1207
|
+
}
|
|
1208
|
+
else if (key === ARROW_DOWN) {
|
|
1209
|
+
selected = (selected + 1) % entries.length;
|
|
1210
|
+
renderBrowser(false);
|
|
1211
|
+
}
|
|
1212
|
+
else if (key === ENTER) {
|
|
1213
|
+
process.stdin.removeListener('data', onKey);
|
|
1214
|
+
process.stdin.setRawMode(false);
|
|
1215
|
+
process.stdin.pause();
|
|
1216
|
+
process.stdout.write('\n');
|
|
1217
|
+
resolve({ exit: false, id: entries[selected].id });
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
});
|
|
1221
|
+
if (action.exit) {
|
|
1222
|
+
running = false;
|
|
1223
|
+
}
|
|
1224
|
+
else if (action.id) {
|
|
1225
|
+
await handleShow(['--id', action.id]);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1130
1229
|
function normalizeCommand(raw) {
|
|
1131
1230
|
const normalized = raw.trim().toLowerCase();
|
|
1132
1231
|
return COMMAND_ALIASES[normalized] ?? null;
|
package/package.json
CHANGED