@shnitzel/plugscout 0.3.12 → 0.3.14

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.
@@ -0,0 +1,20 @@
1
+ .-----------------------------------------.
2
+ | Scouting plugins so you don't have to. |
3
+ '-----------------------------------------'
4
+ \/
5
+ ___________
6
+ _/ ★ ☆ ★ \_
7
+ /_______________\
8
+ |_________________|
9
+ | (◉) (◉) |
10
+ | ∧ |
11
+ | '~~~~~~~' |
12
+ |_________________|
13
+ _/| [ * ] |\_
14
+ / |_______________| \
15
+ | |
16
+ _| |_
17
+ (_) (_)
18
+
19
+ PlugScout {{version}}
20
+ maintained by {{author}}
@@ -11,7 +11,8 @@ export const colors = {
11
11
  red: (value) => wrap(31, value),
12
12
  cyan: (value) => wrap(36, value),
13
13
  gray: (value) => wrap(90, value),
14
- bold: (value) => wrap(1, value)
14
+ bold: (value) => wrap(1, value),
15
+ dim: (value) => wrap(2, value)
15
16
  };
16
17
  export function colorRisk(tier, value) {
17
18
  if (tier === 'low') {
@@ -7,8 +7,10 @@ import { getPackagePath } from '../../../lib/paths.js';
7
7
  import { colors } from '../formatters/colors.js';
8
8
  import { isSetUp, loadCatalogItems } from '../../../api/index.js';
9
9
  export async function renderHomeScreen() {
10
+ const termCols = process.stdout.columns ?? 80;
11
+ const useCompact = termCols < 82;
10
12
  const [logo, pkg, catalogStats, runtimeStats] = await Promise.all([
11
- readLogo(),
13
+ readLogo(useCompact),
12
14
  readPackageMeta(),
13
15
  readCatalogStats(),
14
16
  readRuntimeStats()
@@ -21,40 +23,56 @@ export async function renderHomeScreen() {
21
23
  .replace('{{author}}', author || 'unknown');
22
24
  lines.push(colorIfTty(renderedLogo.trimEnd(), colors.cyan));
23
25
  lines.push('');
24
- lines.push('Discover and safely install Claude plugins, Claude connectors, Copilot extensions, Skills, and MCP servers.');
26
+ lines.push(colorIfTty('Discover and safely install Claude plugins, connectors,', colors.dim));
27
+ lines.push(colorIfTty('Copilot/Cursor/Gemini extensions, Skills, and MCP servers.', colors.dim));
25
28
  lines.push('');
26
29
  lines.push(colorIfTty('Catalog', colors.bold));
27
- lines.push(`- items=${catalogStats.items} skill=${catalogStats.skill} mcp=${catalogStats.mcp} claude-plugin=${catalogStats.claudePlugin} claude-connector=${catalogStats.claudeConnector} copilot-extension=${catalogStats.copilotExtension}`);
28
- lines.push(`- stale-registries=${runtimeStats.staleRegistries} whitelist=${runtimeStats.whitelist} quarantined=${runtimeStats.quarantined}`);
30
+ lines.push(colorIfTty(` items=${catalogStats.items} skill=${catalogStats.skill} mcp=${catalogStats.mcp} claude-plugin=${catalogStats.claudePlugin} claude-connector=${catalogStats.claudeConnector}`, colors.dim));
31
+ lines.push(colorIfTty(` copilot-extension=${catalogStats.copilotExtension} cursor-extension=${catalogStats.cursorExtension} gemini-extension=${catalogStats.geminiExtension}`, colors.dim));
32
+ lines.push(colorIfTty(` stale-registries=${runtimeStats.staleRegistries} whitelist=${runtimeStats.whitelist} quarantined=${runtimeStats.quarantined}`, colors.dim));
29
33
  lines.push('');
30
34
  lines.push(colorIfTty('Quick actions', colors.bold));
31
- lines.push('- plugscout doctor');
32
- lines.push('- plugscout status --verbose');
33
- lines.push('- plugscout recommend --project . --only-safe --limit 10');
34
- lines.push('- plugscout sync --dry-run');
35
- lines.push('- plugscout help');
35
+ for (const cmd of [
36
+ 'plugscout doctor',
37
+ 'plugscout status --verbose',
38
+ 'plugscout recommend --project . --only-safe --limit 10',
39
+ 'plugscout sync --dry-run',
40
+ 'plugscout help',
41
+ ]) {
42
+ lines.push(` ${colorIfTty(cmd, colors.green)}`);
43
+ }
36
44
  lines.push('');
37
45
  lines.push(colorIfTty('Examples', colors.bold));
38
- lines.push('- plugscout list --kind connectors --limit 10');
39
- lines.push('- plugscout search github');
40
- lines.push('- plugscout show --id claude-connector:asana');
46
+ for (const cmd of [
47
+ 'plugscout list --kind connectors --limit 10',
48
+ 'plugscout list --kind cursor --limit 15',
49
+ 'plugscout search github',
50
+ 'plugscout show --id claude-connector:asana',
51
+ ]) {
52
+ lines.push(` ${colorIfTty(cmd, colors.green)}`);
53
+ }
41
54
  lines.push('');
42
55
  lines.push(colorIfTty('Kind aliases', colors.bold));
43
- lines.push('- skills, mcps, plugins, connectors, extensions');
56
+ lines.push(colorIfTty(' skills · mcps · plugins · connectors · extensions · cursor · gemini', colors.dim));
44
57
  lines.push('');
45
58
  lines.push(colorIfTty('Ranking meaning', colors.bold));
46
- lines.push('- `top` and `recommend` are repo-aware suggestions, not global popularity charts.');
47
- lines.push('- score = fit + trust + freshness - security - blocked');
48
- lines.push('- higher score means a better match for this repo under current policy');
49
- lines.push('- review each suggestion before installing; do not install blindly from rank alone');
59
+ lines.push(colorIfTty(' top/recommend output is repo-aware suggestions, not a global popularity chart', colors.dim));
60
+ lines.push(colorIfTty(' score = fit + trust + freshness - security - blocked', colors.dim));
61
+ lines.push(colorIfTty(' review before installing do not install blindly from rank alone', colors.dim));
50
62
  return lines.join('\n');
51
63
  }
52
- async function readLogo() {
64
+ async function readLogo(compact = false) {
65
+ const file = compact ? 'assets/cli/logo-compact.txt' : 'assets/cli/logo.txt';
53
66
  try {
54
- return await fs.readFile(getPackagePath('assets/cli/logo.txt'), 'utf8');
67
+ return await fs.readFile(getPackagePath(file), 'utf8');
55
68
  }
56
69
  catch {
57
- return 'PlugScout';
70
+ try {
71
+ return await fs.readFile(getPackagePath('assets/cli/logo.txt'), 'utf8');
72
+ }
73
+ catch {
74
+ return 'PlugScout';
75
+ }
58
76
  }
59
77
  }
60
78
  async function readPackageMeta() {
@@ -68,38 +86,32 @@ async function readPackageMeta() {
68
86
  }
69
87
  async function readCatalogStats() {
70
88
  const items = await loadCatalogItems();
71
- let skill = 0;
72
- let mcp = 0;
73
- let claudePlugin = 0;
74
- let claudeConnector = 0;
75
- let copilotExtension = 0;
89
+ let skill = 0, mcp = 0, claudePlugin = 0, claudeConnector = 0;
90
+ let copilotExtension = 0, cursorExtension = 0, geminiExtension = 0;
76
91
  items.forEach((item) => {
77
92
  if (item.kind === 'skill') {
78
93
  skill += 1;
79
- return;
80
94
  }
81
- if (item.kind === 'mcp') {
95
+ else if (item.kind === 'mcp') {
82
96
  mcp += 1;
83
- return;
84
97
  }
85
- if (item.kind === 'claude-plugin') {
98
+ else if (item.kind === 'claude-plugin') {
86
99
  claudePlugin += 1;
87
- return;
88
100
  }
89
- if (item.kind === 'claude-connector') {
101
+ else if (item.kind === 'claude-connector') {
90
102
  claudeConnector += 1;
91
- return;
92
103
  }
93
- copilotExtension += 1;
104
+ else if (item.kind === 'cursor-extension') {
105
+ cursorExtension += 1;
106
+ }
107
+ else if (item.kind === 'gemini-extension') {
108
+ geminiExtension += 1;
109
+ }
110
+ else {
111
+ copilotExtension += 1;
112
+ }
94
113
  });
95
- return {
96
- items: items.length,
97
- skill,
98
- mcp,
99
- claudePlugin,
100
- claudeConnector,
101
- copilotExtension
102
- };
114
+ return { items: items.length, skill, mcp, claudePlugin, claudeConnector, copilotExtension, cursorExtension, geminiExtension };
103
115
  }
104
116
  async function readRuntimeStats() {
105
117
  const [syncState, whitelist, quarantine] = await Promise.all([loadSyncState(), loadWhitelist(), loadQuarantine()]);
@@ -192,24 +204,27 @@ export async function renderInteractiveHome() {
192
204
  const CTRL_C = '\u0003';
193
205
  function render(firstRender) {
194
206
  if (!firstRender) {
195
- process.stdout.write(`\x1b[${menuItems.length * 2}A\r`);
207
+ // Restore saved cursor position and clear everything below it.
208
+ // This avoids line-count arithmetic that breaks when descriptions wrap.
209
+ process.stdout.write('\x1b[u\x1b[0J');
196
210
  }
197
211
  for (let i = 0; i < menuItems.length; i++) {
198
212
  const item = menuItems[i];
199
213
  const prefix = i === selected ? ' \u276f ' : ' ';
200
- process.stdout.write(`\x1b[2K${prefix}${item.label}\n`);
214
+ process.stdout.write(`${prefix}${item.label}\n`);
201
215
  if (item.description) {
202
216
  const firstLine = item.description.split('\n')[0];
203
- process.stdout.write(`\x1b[2K \x1b[2m${firstLine}\x1b[0m\n`);
217
+ process.stdout.write(` \x1b[2m${firstLine}\x1b[0m\n`);
204
218
  }
205
219
  else {
206
- process.stdout.write(`\x1b[2K\n`);
220
+ process.stdout.write('\n');
207
221
  }
208
222
  }
209
223
  }
210
224
  let running = true;
211
225
  while (running) {
212
226
  process.stdout.write('\n');
227
+ process.stdout.write('\x1b[s'); // save cursor — used by render(false) to redraw in-place
213
228
  process.stdin.setRawMode(true);
214
229
  process.stdin.resume();
215
230
  process.stdin.setEncoding('utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shnitzel/plugscout",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Claude plugins + Claude connectors + Copilot extensions + Skills + MCP security intelligence framework",
5
5
  "private": false,
6
6
  "type": "module",