@shnitzel/plugscout 0.3.22 → 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.
@@ -1149,8 +1149,6 @@ async function promptResultBrowser(entries) {
1149
1149
  const ENTER = '\r';
1150
1150
  const CTRL_C = '';
1151
1151
  const Q = 'q';
1152
- function isUp(key) { return key === '[A' || key === '\x1b[A'; }
1153
- function isDown(key) { return key === '[B' || key === '\x1b[B'; }
1154
1152
  function visibleStart() {
1155
1153
  return Math.max(0, Math.min(selected - Math.floor(WINDOW / 2), entries.length - WINDOW));
1156
1154
  }
@@ -1192,30 +1190,76 @@ async function promptResultBrowser(entries) {
1192
1190
  process.stdin.setEncoding('utf8');
1193
1191
  renderBrowser(true);
1194
1192
  const action = await new Promise((resolve) => {
1195
- process.stdin.on('data', function onKey(key) {
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
+ }
1196
1228
  if (key === CTRL_C || key === Q) {
1197
- process.stdin.removeListener('data', onKey);
1198
- process.stdin.setRawMode(false);
1199
- process.stdin.pause();
1200
- process.stdout.write('\n');
1201
- resolve({ exit: true });
1229
+ doExit();
1202
1230
  }
1203
- else if (isUp(key)) {
1231
+ else if (key === '\x1b[A' || key === '\x1bOA') {
1232
+ // Single-chunk arrow up (most terminals)
1204
1233
  selected = (selected - 1 + entries.length) % entries.length;
1205
1234
  renderBrowser(false);
1206
1235
  }
1207
- else if (isDown(key)) {
1236
+ else if (key === '\x1b[B' || key === '\x1bOB') {
1237
+ // Single-chunk arrow down (most terminals)
1208
1238
  selected = (selected + 1) % entries.length;
1209
1239
  renderBrowser(false);
1210
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
+ }
1211
1250
  else if (key === ENTER) {
1251
+ if (escTimer) {
1252
+ clearTimeout(escTimer);
1253
+ escTimer = null;
1254
+ }
1212
1255
  process.stdin.removeListener('data', onKey);
1213
1256
  process.stdin.setRawMode(false);
1214
1257
  process.stdin.pause();
1215
1258
  process.stdout.write('\n');
1216
1259
  resolve({ exit: false, id: entries[selected].id });
1217
1260
  }
1218
- });
1261
+ }
1262
+ process.stdin.on('data', onKey);
1219
1263
  });
1220
1264
  if (action.exit) {
1221
1265
  running = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shnitzel/plugscout",
3
- "version": "0.3.22",
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",