@shnitzel/plugscout 0.3.21 → 0.3.23

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.
@@ -1146,12 +1146,9 @@ async function promptResultBrowser(entries) {
1146
1146
  const WINDOW = 8;
1147
1147
  let selected = 0;
1148
1148
  let linesDrawn = 0;
1149
- const ARROW_UP = '[A';
1150
- const ARROW_DOWN = '[B';
1151
1149
  const ENTER = '\r';
1152
1150
  const CTRL_C = '';
1153
1151
  const Q = 'q';
1154
- const ESC = '\x1b';
1155
1152
  function visibleStart() {
1156
1153
  return Math.max(0, Math.min(selected - Math.floor(WINDOW / 2), entries.length - WINDOW));
1157
1154
  }
@@ -1193,30 +1190,76 @@ async function promptResultBrowser(entries) {
1193
1190
  process.stdin.setEncoding('utf8');
1194
1191
  renderBrowser(true);
1195
1192
  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 });
1193
+ let escTimer = null;
1194
+ let pendingEsc = false;
1195
+ function doExit() {
1196
+ if (escTimer) {
1197
+ clearTimeout(escTimer);
1198
+ escTimer = null;
1199
+ }
1200
+ process.stdin.removeListener('data', onKey);
1201
+ process.stdin.setRawMode(false);
1202
+ process.stdin.pause();
1203
+ process.stdout.write('\n');
1204
+ resolve({ exit: true });
1205
+ }
1206
+ function onKey(key) {
1207
+ // Handle second byte of a split escape sequence (e.g. \x1b then [A)
1208
+ if (pendingEsc) {
1209
+ pendingEsc = false;
1210
+ if (escTimer) {
1211
+ clearTimeout(escTimer);
1212
+ escTimer = null;
1213
+ }
1214
+ if (key === '[A') {
1215
+ selected = (selected - 1 + entries.length) % entries.length;
1216
+ renderBrowser(false);
1217
+ return;
1218
+ }
1219
+ if (key === '[B') {
1220
+ selected = (selected + 1) % entries.length;
1221
+ renderBrowser(false);
1222
+ return;
1223
+ }
1224
+ // Standalone Esc followed by something unexpected — still exit
1225
+ doExit();
1226
+ return;
1227
+ }
1228
+ if (key === CTRL_C || key === Q) {
1229
+ doExit();
1203
1230
  }
1204
- else if (key === ARROW_UP) {
1231
+ else if (key === '\x1b[A' || key === '\x1bOA') {
1232
+ // Single-chunk arrow up (most terminals)
1205
1233
  selected = (selected - 1 + entries.length) % entries.length;
1206
1234
  renderBrowser(false);
1207
1235
  }
1208
- else if (key === ARROW_DOWN) {
1236
+ else if (key === '\x1b[B' || key === '\x1bOB') {
1237
+ // Single-chunk arrow down (most terminals)
1209
1238
  selected = (selected + 1) % entries.length;
1210
1239
  renderBrowser(false);
1211
1240
  }
1241
+ else if (key === '\x1b') {
1242
+ // Could be standalone Esc or first byte of split arrow sequence
1243
+ pendingEsc = true;
1244
+ escTimer = setTimeout(() => {
1245
+ pendingEsc = false;
1246
+ escTimer = null;
1247
+ doExit();
1248
+ }, 40);
1249
+ }
1212
1250
  else if (key === ENTER) {
1251
+ if (escTimer) {
1252
+ clearTimeout(escTimer);
1253
+ escTimer = null;
1254
+ }
1213
1255
  process.stdin.removeListener('data', onKey);
1214
1256
  process.stdin.setRawMode(false);
1215
1257
  process.stdin.pause();
1216
1258
  process.stdout.write('\n');
1217
1259
  resolve({ exit: false, id: entries[selected].id });
1218
1260
  }
1219
- });
1261
+ }
1262
+ process.stdin.on('data', onKey);
1220
1263
  });
1221
1264
  if (action.exit) {
1222
1265
  running = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shnitzel/plugscout",
3
- "version": "0.3.21",
3
+ "version": "0.3.23",
4
4
  "description": "Claude plugins + Claude connectors + Copilot extensions + Skills + MCP security intelligence framework",
5
5
  "private": false,
6
6
  "type": "module",