github-search-cli 9.8.3 → 9.10.0
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/CHANGELOG.md +123 -0
- package/README.md +68 -40
- package/dist/cli.js +992 -172
- package/docs/HOW-TO.md +255 -0
- package/package.json +3 -2
- package/release-notes/9.10.0.md +137 -0
- package/release-notes/9.9.0.md +193 -0
- package/src/cli.ts +13 -11
- package/src/errors.ts +35 -11
- package/src/help.ts +21 -4
- package/src/mcp-server.ts +18 -2
- package/src/search-state.ts +8 -10
- package/src/search.ts +683 -11
- package/src/trending.ts +2 -6
- package/src/tui/mouse.ts +349 -0
- package/src/tui.ts +433 -123
- package/src/types.ts +11 -1
- package/src/update-check.ts +72 -6
- package/src/watch.ts +16 -11
package/dist/cli.js
CHANGED
|
@@ -24,11 +24,13 @@ var AuthError = class extends SearchCliError {
|
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
var ForbiddenError = class extends SearchCliError {
|
|
27
|
-
constructor() {
|
|
28
|
-
const msg =
|
|
29
|
-
super(
|
|
27
|
+
constructor(detail = "GitHub API returned 403") {
|
|
28
|
+
const msg = `\u26A0 Forbidden (403) \u2014 ${detail}. Common causes: org SSO not authorizing the token, or a secondary rate limit. Press [t] to change the token`;
|
|
29
|
+
super(msg, msg);
|
|
30
|
+
this.detail = detail;
|
|
30
31
|
this.name = "ForbiddenError";
|
|
31
32
|
}
|
|
33
|
+
detail;
|
|
32
34
|
};
|
|
33
35
|
var RateLimitError = class extends SearchCliError {
|
|
34
36
|
constructor(hasToken, resetSeconds) {
|
|
@@ -39,16 +41,36 @@ var RateLimitError = class extends SearchCliError {
|
|
|
39
41
|
}
|
|
40
42
|
hasToken;
|
|
41
43
|
};
|
|
44
|
+
var BadQueryError = class extends SearchCliError {
|
|
45
|
+
/** A concrete, corrected query the user can retry with. */
|
|
46
|
+
constructor(detail, hint) {
|
|
47
|
+
super(
|
|
48
|
+
`Invalid query: ${detail}`,
|
|
49
|
+
hint ? `\u26A0 Invalid query: ${detail}
|
|
50
|
+
Try: ${hint}` : `\u26A0 Invalid query: ${detail}`
|
|
51
|
+
);
|
|
52
|
+
this.detail = detail;
|
|
53
|
+
this.hint = hint;
|
|
54
|
+
this.name = "BadQueryError";
|
|
55
|
+
}
|
|
56
|
+
detail;
|
|
57
|
+
hint;
|
|
58
|
+
};
|
|
42
59
|
var NoResultsError = class extends SearchCliError {
|
|
43
|
-
|
|
60
|
+
/** Concrete loosened queries to retry, most promising first. */
|
|
61
|
+
constructor(query, relaxations = []) {
|
|
62
|
+
const tips = relaxations.length ? relaxations.map((r) => `Try: ${r}`).join("\n ") : "Tips: check spelling, try fewer qualifiers, or [b]rowse trending";
|
|
44
63
|
super(
|
|
45
|
-
`No results for "${query}"`,
|
|
46
|
-
`\u{1F50D} No results for "${query}".
|
|
64
|
+
relaxations.length > 0 ? `No results for "${query}" \u2014 try loosening: ${relaxations.join(" / ")}` : `No results for "${query}"`,
|
|
65
|
+
`\u{1F50D} No results for "${query}".
|
|
66
|
+
${tips}`
|
|
47
67
|
);
|
|
48
68
|
this.query = query;
|
|
69
|
+
this.relaxations = relaxations;
|
|
49
70
|
this.name = "NoResultsError";
|
|
50
71
|
}
|
|
51
72
|
query;
|
|
73
|
+
relaxations;
|
|
52
74
|
};
|
|
53
75
|
var ParseError = class extends SearchCliError {
|
|
54
76
|
constructor(source, detail) {
|
|
@@ -209,9 +231,11 @@ function normalizeEnvelope(env) {
|
|
|
209
231
|
var KNOWN_QUALIFIERS = [
|
|
210
232
|
"language",
|
|
211
233
|
"stars",
|
|
234
|
+
"forks",
|
|
212
235
|
"fork",
|
|
213
236
|
"archived",
|
|
214
237
|
"topic",
|
|
238
|
+
"topics",
|
|
215
239
|
"user",
|
|
216
240
|
"org",
|
|
217
241
|
"repo",
|
|
@@ -221,8 +245,80 @@ var KNOWN_QUALIFIERS = [
|
|
|
221
245
|
"in",
|
|
222
246
|
"size",
|
|
223
247
|
"license",
|
|
224
|
-
"created"
|
|
248
|
+
"created",
|
|
249
|
+
"followers"
|
|
225
250
|
];
|
|
251
|
+
var KNOWN_QUALIFIER_SET = new Set(KNOWN_QUALIFIERS);
|
|
252
|
+
var QUALIFIER_ALIASES = {
|
|
253
|
+
// language
|
|
254
|
+
langauge: "language",
|
|
255
|
+
lang: "language",
|
|
256
|
+
languge: "language",
|
|
257
|
+
// stars / forks
|
|
258
|
+
star: "stars",
|
|
259
|
+
strs: "stars",
|
|
260
|
+
forke: "forks",
|
|
261
|
+
// topic
|
|
262
|
+
topc: "topic",
|
|
263
|
+
tpics: "topic",
|
|
264
|
+
// user / org
|
|
265
|
+
usr: "user",
|
|
266
|
+
ogranization: "org",
|
|
267
|
+
orginzation: "org",
|
|
268
|
+
// date qualifiers
|
|
269
|
+
push: "pushed",
|
|
270
|
+
creat: "created",
|
|
271
|
+
updat: "updated",
|
|
272
|
+
// visibility
|
|
273
|
+
visiblity: "visibility",
|
|
274
|
+
visability: "visibility",
|
|
275
|
+
// license
|
|
276
|
+
licence: "license",
|
|
277
|
+
// size
|
|
278
|
+
siz: "size",
|
|
279
|
+
// archived
|
|
280
|
+
archive: "archived"
|
|
281
|
+
};
|
|
282
|
+
function editDistanceWithin(a, b, max) {
|
|
283
|
+
if (a === b) return 0;
|
|
284
|
+
if (Math.abs(a.length - b.length) > max) return max + 1;
|
|
285
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
286
|
+
for (let i = 1; i <= a.length; i++) {
|
|
287
|
+
const curr = [i];
|
|
288
|
+
let rowMin = i;
|
|
289
|
+
for (let j = 1; j <= b.length; j++) {
|
|
290
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
291
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
292
|
+
if (curr[j] < rowMin) rowMin = curr[j];
|
|
293
|
+
}
|
|
294
|
+
if (rowMin > max) return max + 1;
|
|
295
|
+
prev = curr;
|
|
296
|
+
}
|
|
297
|
+
return prev[b.length];
|
|
298
|
+
}
|
|
299
|
+
function resolveQualifierKey(key) {
|
|
300
|
+
const k = key.toLowerCase();
|
|
301
|
+
if (KNOWN_QUALIFIER_SET.has(k)) return k;
|
|
302
|
+
const alias = QUALIFIER_ALIASES[k];
|
|
303
|
+
if (alias) return alias;
|
|
304
|
+
if (k.length < 4) return k;
|
|
305
|
+
const maxDistance = k.length <= 5 ? 1 : 2;
|
|
306
|
+
let best;
|
|
307
|
+
let bestDistance = maxDistance + 1;
|
|
308
|
+
let ties = 0;
|
|
309
|
+
for (const known of KNOWN_QUALIFIERS) {
|
|
310
|
+
const d = editDistanceWithin(k, known, maxDistance);
|
|
311
|
+
if (d < bestDistance) {
|
|
312
|
+
bestDistance = d;
|
|
313
|
+
best = known;
|
|
314
|
+
ties = 1;
|
|
315
|
+
if (d === 0) break;
|
|
316
|
+
} else if (d === bestDistance && best !== void 0) {
|
|
317
|
+
ties++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return best !== void 0 && ties === 1 ? best : k;
|
|
321
|
+
}
|
|
226
322
|
var QUALIFIER_SUGGESTIONS = {
|
|
227
323
|
l: ["language:"],
|
|
228
324
|
s: ["stars:", "size:"],
|
|
@@ -238,13 +334,164 @@ var QUALIFIER_SUGGESTIONS = {
|
|
|
238
334
|
a: ["archived:"]
|
|
239
335
|
};
|
|
240
336
|
function suggestFor(input) {
|
|
241
|
-
const trimmed = input.trim().toLowerCase();
|
|
337
|
+
const trimmed = input.trim().toLowerCase().replace(/:$/, "");
|
|
242
338
|
if (trimmed.length === 0) return [];
|
|
243
339
|
if (trimmed.length === 1 && QUALIFIER_SUGGESTIONS[trimmed]) {
|
|
244
340
|
return QUALIFIER_SUGGESTIONS[trimmed];
|
|
245
341
|
}
|
|
246
|
-
const
|
|
247
|
-
|
|
342
|
+
const resolved = resolveQualifierKey(trimmed);
|
|
343
|
+
const aliasHit = resolved !== trimmed && KNOWN_QUALIFIER_SET.has(resolved) ? [`${resolved}:`] : [];
|
|
344
|
+
const matches = KNOWN_QUALIFIERS.filter(
|
|
345
|
+
(k) => k.startsWith(trimmed) && !aliasHit.includes(`${k}:`)
|
|
346
|
+
).map((k) => `${k}:`);
|
|
347
|
+
return [...aliasHit, ...matches];
|
|
348
|
+
}
|
|
349
|
+
var NUMERIC_QUALIFIERS = /* @__PURE__ */ new Set([
|
|
350
|
+
"forks",
|
|
351
|
+
"size",
|
|
352
|
+
"stars",
|
|
353
|
+
"topics",
|
|
354
|
+
"followers"
|
|
355
|
+
]);
|
|
356
|
+
var BOOLEAN_QUALIFIERS = /* @__PURE__ */ new Set(["archived"]);
|
|
357
|
+
var VISIBILITY_VALUES = /* @__PURE__ */ new Set(["public", "private", "internal"]);
|
|
358
|
+
var IN_FIELDS = /* @__PURE__ */ new Set([
|
|
359
|
+
"name",
|
|
360
|
+
"description",
|
|
361
|
+
"topics",
|
|
362
|
+
"readme",
|
|
363
|
+
"comments"
|
|
364
|
+
]);
|
|
365
|
+
var DATE_QUALIFIERS = /* @__PURE__ */ new Set(["created", "pushed", "updated"]);
|
|
366
|
+
var COMPARISON_OPERATORS = [">=", "<=", ">", "<"];
|
|
367
|
+
function redactToken(url, token) {
|
|
368
|
+
return token ? url.replace(token, "<token>") : url;
|
|
369
|
+
}
|
|
370
|
+
function splitComparison(value) {
|
|
371
|
+
for (const op of COMPARISON_OPERATORS) {
|
|
372
|
+
if (value.startsWith(op)) {
|
|
373
|
+
return { op, rest: value.slice(op.length) };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { op: "", rest: value };
|
|
377
|
+
}
|
|
378
|
+
function parseCount(raw) {
|
|
379
|
+
const trimmed = raw.trim().replace(/_/g, "");
|
|
380
|
+
if (trimmed === "") return null;
|
|
381
|
+
const match = /^(\d+(?:\.\d+)?)([km])?$/i.exec(trimmed);
|
|
382
|
+
if (!match) return null;
|
|
383
|
+
if (!match[2] && match[1].includes(".")) return null;
|
|
384
|
+
const base = Number.parseFloat(match[1]);
|
|
385
|
+
if (!Number.isFinite(base)) return null;
|
|
386
|
+
const suffix = match[2]?.toLowerCase();
|
|
387
|
+
const multiplier = suffix === "k" ? 1e3 : suffix === "m" ? 1e6 : 1;
|
|
388
|
+
return Math.floor(base * multiplier);
|
|
389
|
+
}
|
|
390
|
+
function isValidISODate(raw) {
|
|
391
|
+
const trimmed = raw.trim();
|
|
392
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?(?:Z|[+-]\d{2}:?\d{2})?)?$/.exec(
|
|
393
|
+
trimmed
|
|
394
|
+
);
|
|
395
|
+
if (!m) return false;
|
|
396
|
+
const [, y, mo, d, h = "0", mi = "0", s = "0"] = m;
|
|
397
|
+
const year = Number(y);
|
|
398
|
+
const month = Number(mo);
|
|
399
|
+
const day = Number(d);
|
|
400
|
+
if (month < 1 || month > 12) return false;
|
|
401
|
+
if (day < 1 || day > 31) return false;
|
|
402
|
+
if (Number(h) > 23 || Number(mi) > 59 || Number(s) > 59) return false;
|
|
403
|
+
const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
404
|
+
return day <= daysInMonth;
|
|
405
|
+
}
|
|
406
|
+
function normalizeQualifierValue(key, value) {
|
|
407
|
+
const normalizedKey = key.toLowerCase();
|
|
408
|
+
if (!NUMERIC_QUALIFIERS.has(normalizedKey)) return value;
|
|
409
|
+
if (value === "") return value;
|
|
410
|
+
const { op, rest } = splitComparison(value);
|
|
411
|
+
const rangeIdx = rest.indexOf("..");
|
|
412
|
+
if (rangeIdx > 0) {
|
|
413
|
+
const lo = parseCount(rest.slice(0, rangeIdx));
|
|
414
|
+
const hi = parseCount(rest.slice(rangeIdx + 2));
|
|
415
|
+
if (lo === null || hi === null) return value;
|
|
416
|
+
return `${op}${lo}..${hi}`;
|
|
417
|
+
}
|
|
418
|
+
if (!/[km]$/i.test(rest.trim())) return value;
|
|
419
|
+
const count = parseCount(rest);
|
|
420
|
+
if (count === null) return value;
|
|
421
|
+
return `${op || ">="}${count}`;
|
|
422
|
+
}
|
|
423
|
+
function validateQualifierValue(key, value) {
|
|
424
|
+
const k = key.toLowerCase();
|
|
425
|
+
if (!KNOWN_QUALIFIER_SET.has(k)) return null;
|
|
426
|
+
if (value.trim() === "") {
|
|
427
|
+
return `"${key}:" needs a value (e.g. ${key}:<something>)`;
|
|
428
|
+
}
|
|
429
|
+
if (NUMERIC_QUALIFIERS.has(k)) {
|
|
430
|
+
const { rest } = splitComparison(value.trim());
|
|
431
|
+
const rangeIdx = rest.indexOf("..");
|
|
432
|
+
if (rangeIdx > 0) {
|
|
433
|
+
const lo = parseCount(rest.slice(0, rangeIdx));
|
|
434
|
+
const hi = parseCount(rest.slice(rangeIdx + 2));
|
|
435
|
+
if (lo === null || hi === null) {
|
|
436
|
+
return `"${key}:${value}" is not a valid range \u2014 use ${k}:100..500 or ${k}:>=100`;
|
|
437
|
+
}
|
|
438
|
+
if (lo > hi) {
|
|
439
|
+
return `"${key}:${value}" has a reversed range (${lo} > ${hi})`;
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
if (parseCount(rest) === null) {
|
|
444
|
+
return `"${key}:${value}" is not a number \u2014 use ${k}:100, ${k}:>=100, or ${k}:1k..10k`;
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
if (k === "fork") {
|
|
449
|
+
const v = value.trim().toLowerCase();
|
|
450
|
+
if (v !== "true" && v !== "false" && v !== "only") {
|
|
451
|
+
return `"fork:${value}" is not valid \u2014 use fork:true, fork:false, or fork:only`;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
if (BOOLEAN_QUALIFIERS.has(k)) {
|
|
456
|
+
const v = value.trim().toLowerCase();
|
|
457
|
+
if (v !== "true" && v !== "false") {
|
|
458
|
+
return `"${key}:${value}" is not valid \u2014 use ${k}:true or ${k}:false`;
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
if (k === "visibility") {
|
|
463
|
+
const v = value.trim().toLowerCase();
|
|
464
|
+
if (!VISIBILITY_VALUES.has(v)) {
|
|
465
|
+
return `"visibility:${value}" is not valid \u2014 use visibility:public, visibility:private, or visibility:internal`;
|
|
466
|
+
}
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
if (k === "in") {
|
|
470
|
+
const parts = value.split(",").map((p) => p.trim().toLowerCase());
|
|
471
|
+
if (parts.some((p) => !IN_FIELDS.has(p))) {
|
|
472
|
+
const bad = parts.find((p) => !IN_FIELDS.has(p));
|
|
473
|
+
return `"in:${value}" is not valid \u2014 ${bad ? `"${bad}" is not a ` : ""}searchable field. Try in:name, in:description, in:topics, or in:readme`;
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
if (DATE_QUALIFIERS.has(k)) {
|
|
478
|
+
const trimmed = value.trim();
|
|
479
|
+
const { rest } = splitComparison(trimmed);
|
|
480
|
+
const rangeIdx = rest.indexOf("..");
|
|
481
|
+
if (rangeIdx > 0) {
|
|
482
|
+
const lo = rest.slice(0, rangeIdx);
|
|
483
|
+
const hi = rest.slice(rangeIdx + 2);
|
|
484
|
+
if (!isValidISODate(lo) || !isValidISODate(hi)) {
|
|
485
|
+
return `"${key}:${value}" is not a valid date range \u2014 use ${k}:2020-01-01..2024-01-01`;
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
if (!isValidISODate(rest)) {
|
|
490
|
+
return `"${key}:${value}" is not a valid date \u2014 use ${k}:2024-01-01 (optionally with >, >=, <, <=)`;
|
|
491
|
+
}
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
248
495
|
}
|
|
249
496
|
function tokenize(input) {
|
|
250
497
|
const tokens = [];
|
|
@@ -272,22 +519,46 @@ function tokenize(input) {
|
|
|
272
519
|
}
|
|
273
520
|
return tokens;
|
|
274
521
|
}
|
|
522
|
+
var EMPTY_QUERY = {
|
|
523
|
+
keywords: [],
|
|
524
|
+
qualifiers: [],
|
|
525
|
+
raw: "",
|
|
526
|
+
corrections: []
|
|
527
|
+
};
|
|
528
|
+
function trendingQuery() {
|
|
529
|
+
return { ...EMPTY_QUERY, raw: "trending" };
|
|
530
|
+
}
|
|
275
531
|
function parseQuery(raw) {
|
|
276
532
|
const trimmed = raw.trim();
|
|
277
533
|
if (trimmed === "") {
|
|
278
|
-
return { keywords: [], qualifiers: [], raw };
|
|
534
|
+
return { keywords: [], qualifiers: [], raw, corrections: [] };
|
|
279
535
|
}
|
|
280
536
|
const keywords = [];
|
|
281
537
|
const qualifiers = [];
|
|
538
|
+
const corrections = [];
|
|
282
539
|
for (const token of tokenize(trimmed)) {
|
|
283
540
|
const qualifier = matchQualifier(token);
|
|
284
541
|
if (qualifier) {
|
|
285
|
-
|
|
542
|
+
const resolvedKey = resolveQualifierKey(qualifier.key);
|
|
543
|
+
const normalizedValue = normalizeQualifierValue(
|
|
544
|
+
resolvedKey,
|
|
545
|
+
qualifier.value
|
|
546
|
+
);
|
|
547
|
+
const original = `${qualifier.negated ? "-" : ""}${qualifier.key}:${qualifier.value}`;
|
|
548
|
+
const corrected = `${qualifier.negated ? "-" : ""}${resolvedKey}:${normalizedValue}`;
|
|
549
|
+
if (original !== corrected) {
|
|
550
|
+
corrections.push({ from: original, to: corrected });
|
|
551
|
+
}
|
|
552
|
+
qualifiers.push({
|
|
553
|
+
key: resolvedKey,
|
|
554
|
+
value: normalizedValue,
|
|
555
|
+
negated: qualifier.negated
|
|
556
|
+
});
|
|
286
557
|
} else {
|
|
287
558
|
keywords.push(token);
|
|
288
559
|
}
|
|
289
560
|
}
|
|
290
|
-
return { keywords, qualifiers, raw: trimmed };
|
|
561
|
+
return { keywords, qualifiers, raw: trimmed, corrections };
|
|
291
562
|
}
|
|
292
563
|
function matchQualifier(token) {
|
|
293
564
|
const negated = token.startsWith("-");
|
|
@@ -296,8 +567,7 @@ function matchQualifier(token) {
|
|
|
296
567
|
if (idx <= 0) return null;
|
|
297
568
|
const key = body.slice(0, idx).toLowerCase();
|
|
298
569
|
let value = body.slice(idx + 1);
|
|
299
|
-
if (
|
|
300
|
-
if (!/^[a-z]+$/i.test(key)) return null;
|
|
570
|
+
if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/i.test(key)) return null;
|
|
301
571
|
if (value.startsWith("//")) return null;
|
|
302
572
|
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
303
573
|
value = value.slice(1, -1);
|
|
@@ -330,11 +600,143 @@ function applyFlagFilters(query, flags) {
|
|
|
330
600
|
});
|
|
331
601
|
return { ...query, qualifiers };
|
|
332
602
|
}
|
|
603
|
+
function withoutQualifier(query, index) {
|
|
604
|
+
const remaining = query.qualifiers.filter((_, i) => i !== index);
|
|
605
|
+
const parts = [...query.keywords];
|
|
606
|
+
for (const q of remaining) {
|
|
607
|
+
const needsQuote = q.value.includes(" ") && !q.value.startsWith('"');
|
|
608
|
+
const value = needsQuote ? `"${q.value}"` : q.value;
|
|
609
|
+
parts.push(`${q.negated ? "-" : ""}${q.key}:${value}`);
|
|
610
|
+
}
|
|
611
|
+
const text = parts.join(" ").trim();
|
|
612
|
+
return text === "" ? null : text;
|
|
613
|
+
}
|
|
333
614
|
function validateQuery(query) {
|
|
334
615
|
const has = (key) => query.qualifiers.some((q) => q.key === key && !q.negated);
|
|
335
616
|
if (has("visibility") && has("private")) {
|
|
336
|
-
throw new
|
|
617
|
+
throw new BadQueryError(
|
|
618
|
+
"Cannot combine `visibility:` and `private:` filters.",
|
|
619
|
+
`${query.qualifiers.filter((q) => q.key === "private" && !q.negated).map((q) => withoutQualifier(query, query.qualifiers.indexOf(q))).find((v) => v !== null) ?? query.raw}`
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
for (let i = 0; i < query.qualifiers.length; i++) {
|
|
623
|
+
const q = query.qualifiers[i];
|
|
624
|
+
const problem = validateQualifierValue(q.key, q.value);
|
|
625
|
+
if (problem) {
|
|
626
|
+
throw new BadQueryError(problem, withoutQualifier(query, i) ?? void 0);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
var OVER_CONSTRAINT_WEIGHT = {
|
|
631
|
+
size: 100,
|
|
632
|
+
stars: 95,
|
|
633
|
+
forks: 80,
|
|
634
|
+
created: 75,
|
|
635
|
+
pushed: 70,
|
|
636
|
+
updated: 70,
|
|
637
|
+
visibility: 65,
|
|
638
|
+
repo: 60,
|
|
639
|
+
user: 55,
|
|
640
|
+
org: 50,
|
|
641
|
+
topic: 45,
|
|
642
|
+
in: 30,
|
|
643
|
+
license: 25,
|
|
644
|
+
language: 20,
|
|
645
|
+
fork: 15,
|
|
646
|
+
archived: 10
|
|
647
|
+
};
|
|
648
|
+
function loosenNumericBound(value) {
|
|
649
|
+
const { op, rest } = splitComparison(value.trim());
|
|
650
|
+
const count = parseCount(rest);
|
|
651
|
+
if (count === null || count === 0) return null;
|
|
652
|
+
const loosened = Math.max(1, Math.floor(count / 10));
|
|
653
|
+
return `${op || ">="}${loosened}`;
|
|
654
|
+
}
|
|
655
|
+
function loosenDateBound(value) {
|
|
656
|
+
const { op, rest } = splitComparison(value.trim());
|
|
657
|
+
if (!isValidISODate(rest)) return null;
|
|
658
|
+
const year = Number.parseInt(rest.slice(0, 4), 10);
|
|
659
|
+
if (!Number.isFinite(year)) return null;
|
|
660
|
+
const shifted = op === "<" || op === "<=" ? year + 2 : Math.max(1970, year - 2);
|
|
661
|
+
return { op, date: `${shifted}${rest.slice(4)}` };
|
|
662
|
+
}
|
|
663
|
+
function suggestRelaxations(query, limit = 3) {
|
|
664
|
+
const out = [];
|
|
665
|
+
for (let i = 0; i < query.qualifiers.length; i++) {
|
|
666
|
+
const q = query.qualifiers[i];
|
|
667
|
+
const key = q.key.toLowerCase();
|
|
668
|
+
if (q.negated) continue;
|
|
669
|
+
const weight = OVER_CONSTRAINT_WEIGHT[key] ?? 40;
|
|
670
|
+
const emit = (relaxed, qualifier) => {
|
|
671
|
+
const rest = query.qualifiers.filter((_, j) => j !== i).map((other) => {
|
|
672
|
+
const needsQuote = other.value.includes(" ") && !other.value.startsWith('"');
|
|
673
|
+
const value = needsQuote ? `"${other.value}"` : other.value;
|
|
674
|
+
return `${other.negated ? "-" : ""}${other.key}:${value}`;
|
|
675
|
+
});
|
|
676
|
+
const text = [...query.keywords, ...rest, relaxed].join(" ").trim();
|
|
677
|
+
out.push({ suggestion: text, qualifier, confidence: weight });
|
|
678
|
+
};
|
|
679
|
+
if (NUMERIC_QUALIFIERS.has(key) || key === "followers") {
|
|
680
|
+
const rangeIdx = q.value.indexOf("..");
|
|
681
|
+
if (rangeIdx > 0) {
|
|
682
|
+
const hi = parseCount(q.value.slice(rangeIdx + 2));
|
|
683
|
+
if (hi !== null) {
|
|
684
|
+
const lo = q.value.slice(0, rangeIdx);
|
|
685
|
+
emit(`${key}:${lo}..${Math.floor(hi / 10) || hi * 2}`, key);
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
const looser = loosenNumericBound(q.value);
|
|
689
|
+
if (looser) emit(`${key}:${looser}`, key);
|
|
690
|
+
else {
|
|
691
|
+
const dropped2 = withoutQualifier(query, i);
|
|
692
|
+
if (dropped2) {
|
|
693
|
+
out.push({
|
|
694
|
+
suggestion: dropped2,
|
|
695
|
+
qualifier: key,
|
|
696
|
+
confidence: weight - 30
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (DATE_QUALIFIERS.has(key)) {
|
|
704
|
+
const loosened = loosenDateBound(q.value);
|
|
705
|
+
if (loosened) {
|
|
706
|
+
emit(`${key}:${loosened.op || ">="}${loosened.date}`, key);
|
|
707
|
+
} else {
|
|
708
|
+
const dropped2 = withoutQualifier(query, i);
|
|
709
|
+
if (dropped2) {
|
|
710
|
+
out.push({
|
|
711
|
+
suggestion: dropped2,
|
|
712
|
+
qualifier: key,
|
|
713
|
+
confidence: weight - 30
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const dropped = withoutQualifier(query, i);
|
|
720
|
+
if (dropped) {
|
|
721
|
+
out.push({
|
|
722
|
+
suggestion: dropped,
|
|
723
|
+
qualifier: key,
|
|
724
|
+
confidence: weight - 30
|
|
725
|
+
});
|
|
726
|
+
}
|
|
337
727
|
}
|
|
728
|
+
const seen = /* @__PURE__ */ new Set();
|
|
729
|
+
const unique = out.filter((r) => {
|
|
730
|
+
if (seen.has(r.suggestion)) return false;
|
|
731
|
+
seen.add(r.suggestion);
|
|
732
|
+
return true;
|
|
733
|
+
});
|
|
734
|
+
unique.sort((a, b) => b.confidence - a.confidence);
|
|
735
|
+
return unique.slice(0, limit).map((r) => ({
|
|
736
|
+
...r,
|
|
737
|
+
// Clamp so a "drop the qualifier" hint never reports a negative score.
|
|
738
|
+
confidence: Math.max(0, r.confidence)
|
|
739
|
+
}));
|
|
338
740
|
}
|
|
339
741
|
function buildGitHubQuery(query) {
|
|
340
742
|
const parts = [];
|
|
@@ -431,6 +833,11 @@ var GitHubSearchAdapter = class {
|
|
|
431
833
|
});
|
|
432
834
|
const remainingStr = res.headers.get("x-ratelimit-remaining");
|
|
433
835
|
rateLimitRemaining = remainingStr !== null ? Number(remainingStr) : void 0;
|
|
836
|
+
if (process.env.GHFIND_LOG) {
|
|
837
|
+
this.logger.debug(
|
|
838
|
+
`[github] ${res.status} remaining=${remainingStr ?? "none"} url=${redactToken(pagedUrl, token)}`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
434
841
|
if (res.status === 403) {
|
|
435
842
|
if (remainingStr === "0" || remainingStr === null) {
|
|
436
843
|
if (this.tokens.length > 0 && attempt < this.tokens.length) {
|
|
@@ -442,9 +849,18 @@ var GitHubSearchAdapter = class {
|
|
|
442
849
|
}
|
|
443
850
|
const body = await res.text().catch(() => "");
|
|
444
851
|
this.logger.error(
|
|
445
|
-
`[github] non-rate-limit 403:
|
|
852
|
+
`[github] non-rate-limit 403: remaining=${remainingStr} retry-after=${res.headers.get("retry-after") ?? "none"} x-ratelimit-reset=${res.headers.get("x-ratelimit-reset") ?? "none"} authed=${token ? "yes" : "no"} url=${redactToken(pagedUrl, token)}`
|
|
446
853
|
);
|
|
447
|
-
|
|
854
|
+
this.logger.error(`[github] 403 body: ${body.slice(0, 400)}`);
|
|
855
|
+
let reason = "no reason given";
|
|
856
|
+
try {
|
|
857
|
+
const parsed2 = JSON.parse(body);
|
|
858
|
+
if (typeof parsed2.message === "string" && parsed2.message) {
|
|
859
|
+
reason = parsed2.message;
|
|
860
|
+
}
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
throw new ForbiddenError(reason);
|
|
448
864
|
}
|
|
449
865
|
if (res.status === 401) {
|
|
450
866
|
const body = await res.text().catch(() => "");
|
|
@@ -1737,7 +2153,7 @@ var cached = null;
|
|
|
1737
2153
|
function getVersion() {
|
|
1738
2154
|
if (cached) return cached;
|
|
1739
2155
|
if (true) {
|
|
1740
|
-
cached = "9.
|
|
2156
|
+
cached = "9.10.0";
|
|
1741
2157
|
return cached;
|
|
1742
2158
|
}
|
|
1743
2159
|
try {
|
|
@@ -1996,22 +2412,22 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1996
2412
|
let previous = [];
|
|
1997
2413
|
let failCount = 0;
|
|
1998
2414
|
let intervalId = null;
|
|
2415
|
+
if (!opts.trending) {
|
|
2416
|
+
validateQuery(applyFlagFilters(parseQuery(opts.query), {}));
|
|
2417
|
+
}
|
|
1999
2418
|
const tick = async () => {
|
|
2000
2419
|
try {
|
|
2001
2420
|
let repos;
|
|
2002
2421
|
if (opts.trending) {
|
|
2003
2422
|
const since = opts.query === "weekly" ? "weekly" : opts.query === "monthly" ? "monthly" : "daily";
|
|
2004
2423
|
const trendingSearch = createTrendingSearch();
|
|
2005
|
-
const response = await trendingSearch.search(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
trendingSince: since
|
|
2013
|
-
}
|
|
2014
|
-
);
|
|
2424
|
+
const response = await trendingSearch.search(trendingQuery(), {
|
|
2425
|
+
limit: opts.limit,
|
|
2426
|
+
sort: opts.sort,
|
|
2427
|
+
json: false,
|
|
2428
|
+
verbose: false,
|
|
2429
|
+
trendingSince: since
|
|
2430
|
+
});
|
|
2015
2431
|
repos = response.repos;
|
|
2016
2432
|
} else {
|
|
2017
2433
|
const parsed = applyFlagFilters(parseQuery(opts.query), {});
|
|
@@ -3422,6 +3838,7 @@ import {
|
|
|
3422
3838
|
SelectRenderable,
|
|
3423
3839
|
ScrollBoxRenderable as ScrollBoxRenderable3
|
|
3424
3840
|
} from "@opentui/core";
|
|
3841
|
+
import { appendFileSync as appendFileSync2 } from "fs";
|
|
3425
3842
|
|
|
3426
3843
|
// src/trending.ts
|
|
3427
3844
|
import {
|
|
@@ -3457,6 +3874,135 @@ if (import.meta.main && false) {
|
|
|
3457
3874
|
launchTrending();
|
|
3458
3875
|
}
|
|
3459
3876
|
|
|
3877
|
+
// src/tui/mouse.ts
|
|
3878
|
+
var DOUBLE_CLICK_MS = 400;
|
|
3879
|
+
function hitIndexForY(geometry, screenY) {
|
|
3880
|
+
const pitch = geometry.linesPerItem;
|
|
3881
|
+
if (pitch <= 0) return -1;
|
|
3882
|
+
if (geometry.optionCount <= 0) return -1;
|
|
3883
|
+
const localY = screenY - geometry.screenY;
|
|
3884
|
+
if (localY < 0 || localY >= geometry.height) return -1;
|
|
3885
|
+
const row = Math.floor(localY / pitch);
|
|
3886
|
+
const index = geometry.scrollOffset + row;
|
|
3887
|
+
const last = geometry.optionCount - 1;
|
|
3888
|
+
if (index < 0) return 0;
|
|
3889
|
+
return index > last ? last : index;
|
|
3890
|
+
}
|
|
3891
|
+
function selectRowGeometry(select) {
|
|
3892
|
+
const internal = select;
|
|
3893
|
+
return {
|
|
3894
|
+
screenY: select.screenY,
|
|
3895
|
+
height: select.height,
|
|
3896
|
+
optionCount: select.options.length,
|
|
3897
|
+
scrollOffset: internal.scrollOffset ?? 0,
|
|
3898
|
+
linesPerItem: internal.linesPerItem ?? 0
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
function attachClickToSelect(renderer, select, options = {}) {
|
|
3902
|
+
const wheelStep = options.wheelStep ?? 3;
|
|
3903
|
+
let lastClickIndex = -1;
|
|
3904
|
+
let lastClickAt = 0;
|
|
3905
|
+
let lastHovered = -1;
|
|
3906
|
+
select.onMouseDown = (event) => {
|
|
3907
|
+
event.stopPropagation();
|
|
3908
|
+
if (options.keepFocus) event.preventDefault();
|
|
3909
|
+
const index = hitIndexForY(selectRowGeometry(select), event.y);
|
|
3910
|
+
if (index < 0) {
|
|
3911
|
+
lastClickIndex = -1;
|
|
3912
|
+
options.onClickMiss?.();
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
const now = Date.now();
|
|
3916
|
+
const alreadySelected = select.getSelectedIndex() === index;
|
|
3917
|
+
const isDouble = alreadySelected && index === lastClickIndex && now - lastClickAt < DOUBLE_CLICK_MS;
|
|
3918
|
+
lastClickIndex = index;
|
|
3919
|
+
lastClickAt = now;
|
|
3920
|
+
if (isDouble) {
|
|
3921
|
+
lastClickIndex = -1;
|
|
3922
|
+
options.onOpen?.();
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
select.setSelectedIndex(index);
|
|
3926
|
+
};
|
|
3927
|
+
select.onMouseScroll = (event) => {
|
|
3928
|
+
event.stopPropagation();
|
|
3929
|
+
const direction = event.scroll?.direction;
|
|
3930
|
+
if (direction === "up") select.moveUp(wheelStep);
|
|
3931
|
+
else if (direction === "down") select.moveDown(wheelStep);
|
|
3932
|
+
else return;
|
|
3933
|
+
renderer.requestRender();
|
|
3934
|
+
};
|
|
3935
|
+
attachHoverHighlight(renderer, select, {
|
|
3936
|
+
onHover: (index) => {
|
|
3937
|
+
lastHovered = index;
|
|
3938
|
+
options.onHoverRow?.(index);
|
|
3939
|
+
},
|
|
3940
|
+
// A stale row must never outlive the options it pointed at.
|
|
3941
|
+
onLeave: () => {
|
|
3942
|
+
lastHovered = -1;
|
|
3943
|
+
}
|
|
3944
|
+
});
|
|
3945
|
+
return {
|
|
3946
|
+
reset() {
|
|
3947
|
+
lastClickIndex = -1;
|
|
3948
|
+
lastClickAt = 0;
|
|
3949
|
+
},
|
|
3950
|
+
get lastHovered() {
|
|
3951
|
+
return lastHovered;
|
|
3952
|
+
}
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
function attachHoverHighlight(renderer, select, options = {}) {
|
|
3956
|
+
const idleBackground = select.backgroundColor;
|
|
3957
|
+
const hoverBackground = "#ffffff0f";
|
|
3958
|
+
let hovered = -1;
|
|
3959
|
+
select.onMouseOver = () => {
|
|
3960
|
+
renderer.setMousePointer("pointer");
|
|
3961
|
+
select.backgroundColor = hoverBackground;
|
|
3962
|
+
renderer.requestRender();
|
|
3963
|
+
};
|
|
3964
|
+
select.onMouseOut = () => {
|
|
3965
|
+
renderer.setMousePointer("default");
|
|
3966
|
+
select.backgroundColor = idleBackground;
|
|
3967
|
+
if (hovered !== -1) {
|
|
3968
|
+
hovered = -1;
|
|
3969
|
+
options.onLeave?.();
|
|
3970
|
+
}
|
|
3971
|
+
renderer.requestRender();
|
|
3972
|
+
};
|
|
3973
|
+
select.onMouseMove = (event) => {
|
|
3974
|
+
const index = hitIndexForY(selectRowGeometry(select), event.y);
|
|
3975
|
+
if (index === hovered) return;
|
|
3976
|
+
hovered = index;
|
|
3977
|
+
options.onHover?.(index);
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
function attachClickOutside(target, onDismiss) {
|
|
3981
|
+
target.onMouseDown = (event) => {
|
|
3982
|
+
event.stopPropagation();
|
|
3983
|
+
onDismiss();
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
function attachClickableBox(renderer, box, handlers) {
|
|
3987
|
+
box.onMouseDown = (event) => {
|
|
3988
|
+
event.stopPropagation();
|
|
3989
|
+
handlers.onActivate();
|
|
3990
|
+
};
|
|
3991
|
+
box.onMouseOver = () => {
|
|
3992
|
+
renderer.setMousePointer("pointer");
|
|
3993
|
+
box.borderColor = handlers.activeBorder;
|
|
3994
|
+
handlers.onStateChange?.();
|
|
3995
|
+
renderer.requestRender();
|
|
3996
|
+
};
|
|
3997
|
+
box.onMouseOut = () => {
|
|
3998
|
+
renderer.setMousePointer("default");
|
|
3999
|
+
box.borderColor = handlers.idleBorder;
|
|
4000
|
+
handlers.onStateChange?.();
|
|
4001
|
+
renderer.requestRender();
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
var NON_INTERACTIVE_TEXT = { selectable: false };
|
|
4005
|
+
|
|
3460
4006
|
// src/help.ts
|
|
3461
4007
|
function buildHelpSections() {
|
|
3462
4008
|
return [
|
|
@@ -3478,7 +4024,7 @@ function buildHelpSections() {
|
|
|
3478
4024
|
{ keys: "\u2191 \u2193 / j k", action: "Move selection" },
|
|
3479
4025
|
{ keys: "Enter", action: "Open repo in browser" },
|
|
3480
4026
|
{ keys: "/", action: "Focus search input" },
|
|
3481
|
-
{ keys: "
|
|
4027
|
+
{ keys: "Esc", action: "Open command menu" },
|
|
3482
4028
|
{ keys: "? / Ctrl+H", action: "Toggle this help screen" },
|
|
3483
4029
|
{ keys: "q", action: "Quit" }
|
|
3484
4030
|
]
|
|
@@ -3512,7 +4058,8 @@ function buildHelpSections() {
|
|
|
3512
4058
|
rows: [
|
|
3513
4059
|
{ keys: "1-5", action: "Switch tab: Today \u2192 Week \u2192 Month \u2192 \u2026" },
|
|
3514
4060
|
{ keys: "\u2190 \u2192 / h l", action: "Navigate tabs" }
|
|
3515
|
-
]
|
|
4061
|
+
],
|
|
4062
|
+
note: "Tabs are also clickable."
|
|
3516
4063
|
},
|
|
3517
4064
|
{
|
|
3518
4065
|
title: " Packages Mode ",
|
|
@@ -3521,12 +4068,12 @@ function buildHelpSections() {
|
|
|
3521
4068
|
{ keys: "/", action: "Search npm packages" },
|
|
3522
4069
|
{ keys: "\u2191 \u2193 / j k", action: "Move selection" },
|
|
3523
4070
|
{ keys: "Enter", action: "View package details" },
|
|
3524
|
-
{ keys: "
|
|
4071
|
+
{ keys: "Esc", action: "Open command menu" }
|
|
3525
4072
|
],
|
|
3526
4073
|
note: "Search by name, description, or tags. Sort by downloads or score."
|
|
3527
4074
|
},
|
|
3528
4075
|
{
|
|
3529
|
-
title: " Command Menu (
|
|
4076
|
+
title: " Command Menu (Esc)",
|
|
3530
4077
|
titleColor: "purple",
|
|
3531
4078
|
rows: [
|
|
3532
4079
|
{ keys: "Sort", action: "Cycle sort: best-match \u2192 stars \u2192 \u2026" },
|
|
@@ -3551,7 +4098,7 @@ function buildHelpSections() {
|
|
|
3551
4098
|
{ keys: "History", action: "View search history" },
|
|
3552
4099
|
{ keys: "Help", action: "Show this help screen" }
|
|
3553
4100
|
],
|
|
3554
|
-
note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc"
|
|
4101
|
+
note: "Navigate menu with \u2191 \u2193, select with Enter, back with \u2190 / Esc. Clicking a row runs it directly. Esc (or the \u2630 menu button) opens this menu; Space types a space in the query."
|
|
3555
4102
|
},
|
|
3556
4103
|
{
|
|
3557
4104
|
title: " Bookmarks Page ",
|
|
@@ -3564,6 +4111,22 @@ function buildHelpSections() {
|
|
|
3564
4111
|
],
|
|
3565
4112
|
note: "A full page, not an overlay \u2014 Esc returns to the mode menu."
|
|
3566
4113
|
},
|
|
4114
|
+
{
|
|
4115
|
+
title: " Mouse ",
|
|
4116
|
+
titleColor: "green",
|
|
4117
|
+
rows: [
|
|
4118
|
+
{ keys: "Click", action: "Select a row / card / tab" },
|
|
4119
|
+
{ keys: "Double-click", action: "Open the repo or run the action" },
|
|
4120
|
+
{ keys: "Scroll wheel", action: "Move the selection / scroll a pane" },
|
|
4121
|
+
{ keys: "Click outside", action: "Dismiss an open overlay" },
|
|
4122
|
+
{ keys: "Click query box", action: "Focus the search input" },
|
|
4123
|
+
{
|
|
4124
|
+
keys: "\u2630 menu button",
|
|
4125
|
+
action: "Open the command menu (bottom-right)"
|
|
4126
|
+
}
|
|
4127
|
+
],
|
|
4128
|
+
note: "Every list is fully clickable; keyboard and mouse are interchangeable."
|
|
4129
|
+
},
|
|
3567
4130
|
{
|
|
3568
4131
|
title: " Overlay Keys ",
|
|
3569
4132
|
titleColor: "teal",
|
|
@@ -4455,19 +5018,40 @@ function shouldCheckUpdate() {
|
|
|
4455
5018
|
if (Date.now() - state.lastCheck < CHECK_INTERVAL_MS) return false;
|
|
4456
5019
|
return true;
|
|
4457
5020
|
}
|
|
5021
|
+
function semverParts(v) {
|
|
5022
|
+
const core = v.trim().replace(/^v/i, "").split(/[-+]/)[0];
|
|
5023
|
+
return core.split(".").map((s) => {
|
|
5024
|
+
const n = Number(s);
|
|
5025
|
+
return Number.isFinite(n) ? n : 0;
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
function isPrerelease(v) {
|
|
5029
|
+
const core = v.trim().replace(/^v/i, "");
|
|
5030
|
+
const plus = core.indexOf("+");
|
|
5031
|
+
const coreOnly = plus === -1 ? core : core.slice(0, plus);
|
|
5032
|
+
return coreOnly.includes("-");
|
|
5033
|
+
}
|
|
4458
5034
|
function isNewerVersion(current, latest) {
|
|
4459
5035
|
if (typeof Bun !== "undefined" && Bun.semver) {
|
|
4460
5036
|
return Bun.semver.order(current, latest) < 0;
|
|
4461
5037
|
}
|
|
4462
|
-
const curParts = current
|
|
4463
|
-
const newParts = latest
|
|
5038
|
+
const curParts = semverParts(current);
|
|
5039
|
+
const newParts = semverParts(latest);
|
|
5040
|
+
let order = 0;
|
|
4464
5041
|
for (let i = 0; i < Math.max(curParts.length, newParts.length); i++) {
|
|
4465
5042
|
const c = curParts[i] ?? 0;
|
|
4466
5043
|
const n = newParts[i] ?? 0;
|
|
4467
|
-
if (n > c)
|
|
4468
|
-
|
|
5044
|
+
if (n > c) {
|
|
5045
|
+
order = 1;
|
|
5046
|
+
break;
|
|
5047
|
+
}
|
|
5048
|
+
if (n < c) {
|
|
5049
|
+
order = -1;
|
|
5050
|
+
break;
|
|
5051
|
+
}
|
|
4469
5052
|
}
|
|
4470
|
-
return
|
|
5053
|
+
if (order !== 0) return order > 0;
|
|
5054
|
+
return isPrerelease(current) && !isPrerelease(latest);
|
|
4471
5055
|
}
|
|
4472
5056
|
async function checkForUpdate(currentVersion) {
|
|
4473
5057
|
if (process.env.DEBUG)
|
|
@@ -4574,6 +5158,17 @@ function recordPreUpdateState(currentVersion) {
|
|
|
4574
5158
|
lastInstalledVersion: currentVersion
|
|
4575
5159
|
});
|
|
4576
5160
|
}
|
|
5161
|
+
function markPostUpgradeSeen(currentVersion) {
|
|
5162
|
+
try {
|
|
5163
|
+
const state = readUpdateState();
|
|
5164
|
+
if (state.lastInstalledVersion === currentVersion) return;
|
|
5165
|
+
if (isNewerVersion(currentVersion, state.lastInstalledVersion ?? "")) {
|
|
5166
|
+
return;
|
|
5167
|
+
}
|
|
5168
|
+
writeUpdateState({ ...state, lastInstalledVersion: currentVersion });
|
|
5169
|
+
} catch {
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
4577
5172
|
function fetchReleaseNotes(version) {
|
|
4578
5173
|
try {
|
|
4579
5174
|
const notesPath = join6(PKG_DIR2, "release-notes", `${version}.md`);
|
|
@@ -4648,16 +5243,33 @@ var colors = {
|
|
|
4648
5243
|
surfaceDim: "#1a1919",
|
|
4649
5244
|
borderAccent: "#1a1919"
|
|
4650
5245
|
};
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
}
|
|
4660
|
-
|
|
5246
|
+
function createTuiLogger() {
|
|
5247
|
+
const target = process.env.GHFIND_LOG;
|
|
5248
|
+
if (!target) {
|
|
5249
|
+
return { debug: () => {
|
|
5250
|
+
}, info: () => {
|
|
5251
|
+
}, warn: () => {
|
|
5252
|
+
}, error: () => {
|
|
5253
|
+
} };
|
|
5254
|
+
}
|
|
5255
|
+
const write = (level, msg) => {
|
|
5256
|
+
try {
|
|
5257
|
+
appendFileSync2(
|
|
5258
|
+
target,
|
|
5259
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${msg}
|
|
5260
|
+
`
|
|
5261
|
+
);
|
|
5262
|
+
} catch {
|
|
5263
|
+
}
|
|
5264
|
+
};
|
|
5265
|
+
return {
|
|
5266
|
+
debug: (m) => write("debug", m),
|
|
5267
|
+
info: (m) => write("info", m),
|
|
5268
|
+
warn: (m) => write("warn", m),
|
|
5269
|
+
error: (m) => write("error", m)
|
|
5270
|
+
};
|
|
5271
|
+
}
|
|
5272
|
+
var logger = createTuiLogger();
|
|
4661
5273
|
var SORT_MODES = [
|
|
4662
5274
|
{ key: "best-match", label: "best-match" },
|
|
4663
5275
|
{ key: "stars", label: "stars" },
|
|
@@ -4731,7 +5343,7 @@ async function launchBrowser(theme_override) {
|
|
|
4731
5343
|
const compareList = [];
|
|
4732
5344
|
const _quitArmed = false;
|
|
4733
5345
|
const header = new TextRenderable5(renderer, {
|
|
4734
|
-
content: " ghfind \u2014 GitHub repo browser [/]search [
|
|
5346
|
+
content: " ghfind \u2014 GitHub repo browser [/]search [Esc]menu [\u2192]open [?]help [q]uit \xB7 click select \xB7 dbl-click open",
|
|
4735
5347
|
bg: colors.bg,
|
|
4736
5348
|
fg: colors.muted,
|
|
4737
5349
|
height: 1
|
|
@@ -4752,15 +5364,25 @@ async function launchBrowser(theme_override) {
|
|
|
4752
5364
|
const isActive = name === trendingTab;
|
|
4753
5365
|
const label = ` ${name} `;
|
|
4754
5366
|
const tt = new TextRenderable5(renderer, {
|
|
5367
|
+
...NON_INTERACTIVE_TEXT,
|
|
4755
5368
|
content: label,
|
|
4756
5369
|
fg: isActive ? colors.bg : colors.muted,
|
|
4757
5370
|
bg: isActive ? colors.blue : colors.bg,
|
|
4758
5371
|
height: 1
|
|
4759
5372
|
});
|
|
5373
|
+
tt.onMouseDown = (event) => {
|
|
5374
|
+
event.stopPropagation();
|
|
5375
|
+
if (name === trendingTab) return;
|
|
5376
|
+
trendingTab = name;
|
|
5377
|
+
loadTrending();
|
|
5378
|
+
};
|
|
5379
|
+
tt.onMouseOver = () => renderer.setMousePointer("pointer");
|
|
5380
|
+
tt.onMouseOut = () => renderer.setMousePointer("default");
|
|
4760
5381
|
trendingTabTexts.push(tt);
|
|
4761
5382
|
trendingTabBox.add(tt);
|
|
4762
5383
|
if (i < TAB_NAMES.length - 1) {
|
|
4763
5384
|
const sp = new TextRenderable5(renderer, {
|
|
5385
|
+
...NON_INTERACTIVE_TEXT,
|
|
4764
5386
|
content: " ",
|
|
4765
5387
|
fg: colors.muted,
|
|
4766
5388
|
bg: colors.bg,
|
|
@@ -4848,7 +5470,10 @@ async function launchBrowser(theme_override) {
|
|
|
4848
5470
|
const detailText = new TextRenderable5(renderer, {
|
|
4849
5471
|
content: "",
|
|
4850
5472
|
fg: colors.text,
|
|
4851
|
-
wrapMode: "none"
|
|
5473
|
+
wrapMode: "none",
|
|
5474
|
+
// Non-interactive: a left-press here would otherwise be swallowed as a
|
|
5475
|
+
// text-selection drag instead of reaching the click handlers.
|
|
5476
|
+
selectable: false
|
|
4852
5477
|
});
|
|
4853
5478
|
detailBox.add(detailText);
|
|
4854
5479
|
body.add(resultsBox);
|
|
@@ -4863,6 +5488,7 @@ async function launchBrowser(theme_override) {
|
|
|
4863
5488
|
});
|
|
4864
5489
|
root.add(landingBox);
|
|
4865
5490
|
const landingBanner = new TextRenderable5(renderer, {
|
|
5491
|
+
...NON_INTERACTIVE_TEXT,
|
|
4866
5492
|
content: [
|
|
4867
5493
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
4868
5494
|
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
@@ -4940,7 +5566,8 @@ async function launchBrowser(theme_override) {
|
|
|
4940
5566
|
card.add(spacerBot);
|
|
4941
5567
|
}
|
|
4942
5568
|
const landingHint = new TextRenderable5(renderer, {
|
|
4943
|
-
|
|
5569
|
+
...NON_INTERACTIVE_TEXT,
|
|
5570
|
+
content: " \u2191\u2193 Navigate \u21E9 Select [b]ookmarks [?]help [q]uit \xB7 click to pick a mode",
|
|
4944
5571
|
fg: colors.muted,
|
|
4945
5572
|
bg: colors.bg,
|
|
4946
5573
|
height: 1,
|
|
@@ -4968,6 +5595,28 @@ async function launchBrowser(theme_override) {
|
|
|
4968
5595
|
}
|
|
4969
5596
|
}
|
|
4970
5597
|
}
|
|
5598
|
+
landingCards.forEach((card, i) => {
|
|
5599
|
+
let lastClickAt = 0;
|
|
5600
|
+
card.onMouseDown = (event) => {
|
|
5601
|
+
event.stopPropagation();
|
|
5602
|
+
const now = Date.now();
|
|
5603
|
+
const isDouble = landingSelected === i && now - lastClickAt < DOUBLE_CLICK_MS;
|
|
5604
|
+
lastClickAt = now;
|
|
5605
|
+
if (isDouble) {
|
|
5606
|
+
landingOptions[i].action();
|
|
5607
|
+
return;
|
|
5608
|
+
}
|
|
5609
|
+
landingSelected = i;
|
|
5610
|
+
updateLandingCards();
|
|
5611
|
+
renderer.requestRender();
|
|
5612
|
+
};
|
|
5613
|
+
card.onMouseOver = () => {
|
|
5614
|
+
renderer.setMousePointer("pointer");
|
|
5615
|
+
};
|
|
5616
|
+
card.onMouseOut = () => {
|
|
5617
|
+
renderer.setMousePointer("default");
|
|
5618
|
+
};
|
|
5619
|
+
});
|
|
4971
5620
|
renderer.keyInput.on("keypress", (key) => {
|
|
4972
5621
|
if (!landingKeysActive(currentMode, currentOverlay)) return;
|
|
4973
5622
|
if (key.name === "up" || key.name === "k") {
|
|
@@ -5032,6 +5681,7 @@ async function launchBrowser(theme_override) {
|
|
|
5032
5681
|
updateDim.visible = false;
|
|
5033
5682
|
if (type === "none") {
|
|
5034
5683
|
currentOverlay = "none";
|
|
5684
|
+
menuButton.visible = true;
|
|
5035
5685
|
if (currentMode === "landing") {
|
|
5036
5686
|
landingBox.visible = true;
|
|
5037
5687
|
body.visible = false;
|
|
@@ -5045,6 +5695,7 @@ async function launchBrowser(theme_override) {
|
|
|
5045
5695
|
return;
|
|
5046
5696
|
}
|
|
5047
5697
|
currentOverlay = type;
|
|
5698
|
+
menuButton.visible = false;
|
|
5048
5699
|
landingBox.visible = false;
|
|
5049
5700
|
if (type !== "leader") {
|
|
5050
5701
|
hideMainContent();
|
|
@@ -5306,7 +5957,8 @@ async function launchBrowser(theme_override) {
|
|
|
5306
5957
|
const compareText = new TextRenderable5(renderer, {
|
|
5307
5958
|
content: "",
|
|
5308
5959
|
fg: colors.text,
|
|
5309
|
-
bg: colors.bg
|
|
5960
|
+
bg: colors.bg,
|
|
5961
|
+
selectable: false
|
|
5310
5962
|
});
|
|
5311
5963
|
compareBox.add(compareText);
|
|
5312
5964
|
root.add(compareBox);
|
|
@@ -5435,7 +6087,8 @@ async function launchBrowser(theme_override) {
|
|
|
5435
6087
|
itemSpacing: 0
|
|
5436
6088
|
});
|
|
5437
6089
|
const leaderFooter = new TextRenderable5(renderer, {
|
|
5438
|
-
|
|
6090
|
+
...NON_INTERACTIVE_TEXT,
|
|
6091
|
+
content: " \u2191\u2193 select Enter run Esc/q close \xB7 click to run click outside to close ",
|
|
5439
6092
|
fg: colors.muted,
|
|
5440
6093
|
bg: colors.surfaceDim,
|
|
5441
6094
|
height: 1,
|
|
@@ -5567,10 +6220,59 @@ async function launchBrowser(theme_override) {
|
|
|
5567
6220
|
neverText.fg = updateSelectedOption === 2 ? colors.yellow : colors.text;
|
|
5568
6221
|
renderer.requestRender();
|
|
5569
6222
|
}
|
|
6223
|
+
function runUpdateOption(index) {
|
|
6224
|
+
updateSelectedOption = index;
|
|
6225
|
+
renderUpdateOptions();
|
|
6226
|
+
const action = ["now", "later", "never"][index];
|
|
6227
|
+
if (action === "now") {
|
|
6228
|
+
showOverlay("none");
|
|
6229
|
+
recordPreUpdateState(cachedVersion ?? "");
|
|
6230
|
+
performUpdate().then((ok) => {
|
|
6231
|
+
if (ok) {
|
|
6232
|
+
setStatus("\u2713 Update complete \u2014 restart ghfind");
|
|
6233
|
+
} else {
|
|
6234
|
+
setStatus("\u2717 Update failed \u2014 check terminal output");
|
|
6235
|
+
}
|
|
6236
|
+
}).catch(() => {
|
|
6237
|
+
setStatus("\u2717 Update failed \u2014 check terminal output");
|
|
6238
|
+
});
|
|
6239
|
+
} else if (action === "never") {
|
|
6240
|
+
suppressUpdateNotices();
|
|
6241
|
+
showOverlay("none");
|
|
6242
|
+
setStatus("Update notices suppressed");
|
|
6243
|
+
} else {
|
|
6244
|
+
snoozeUpdateNotices(3);
|
|
6245
|
+
showOverlay("none");
|
|
6246
|
+
setStatus("Update reminder snoozed for 3 days");
|
|
6247
|
+
}
|
|
6248
|
+
renderer.requestRender();
|
|
6249
|
+
}
|
|
6250
|
+
const updateCards = [
|
|
6251
|
+
[updateNowBox, 0],
|
|
6252
|
+
[laterBox, 1],
|
|
6253
|
+
[neverBox, 2]
|
|
6254
|
+
];
|
|
6255
|
+
for (const [box, index] of updateCards) {
|
|
6256
|
+
attachClickableBox(renderer, box, {
|
|
6257
|
+
onActivate: () => runUpdateOption(index),
|
|
6258
|
+
idleBorder: updateSelectedOption === index ? colors.yellow : colors.border,
|
|
6259
|
+
activeBorder: colors.yellow,
|
|
6260
|
+
// Hovering must not fight the selection highlight, so it only moves the
|
|
6261
|
+
// pointer cursor; the border stays owned by renderUpdateOptions().
|
|
6262
|
+
onStateChange: () => {
|
|
6263
|
+
box.borderColor = updateSelectedOption === index ? colors.yellow : colors.border;
|
|
6264
|
+
}
|
|
6265
|
+
});
|
|
6266
|
+
}
|
|
6267
|
+
attachClickOutside(updateDim, () => {
|
|
6268
|
+
showOverlay("none");
|
|
6269
|
+
renderer.requestRender();
|
|
6270
|
+
});
|
|
5570
6271
|
updateOptionsRow.add(updateNowBox);
|
|
5571
6272
|
updateOptionsRow.add(laterBox);
|
|
5572
6273
|
const updateFooter = new TextRenderable5(renderer, {
|
|
5573
|
-
|
|
6274
|
+
...NON_INTERACTIVE_TEXT,
|
|
6275
|
+
content: " \u2191\u2193/jk/PgUp/PgDn scroll \u2190\u2192 select Enter Esc/q close \xB7 click a button click outside to close",
|
|
5574
6276
|
fg: colors.muted,
|
|
5575
6277
|
bg: colors.surfaceDim,
|
|
5576
6278
|
height: 1,
|
|
@@ -5926,6 +6628,7 @@ async function launchBrowser(theme_override) {
|
|
|
5926
6628
|
if (!state.lastInstalledVersion) return;
|
|
5927
6629
|
if (state.lastInstalledVersion === currentVersion) return;
|
|
5928
6630
|
const notes = fetchReleaseNotes(currentVersion);
|
|
6631
|
+
markPostUpgradeSeen(currentVersion);
|
|
5929
6632
|
updateSelectedOption = 0;
|
|
5930
6633
|
updateHeader.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
|
|
5931
6634
|
`;
|
|
@@ -6204,6 +6907,40 @@ async function launchBrowser(theme_override) {
|
|
|
6204
6907
|
paddingX: 1
|
|
6205
6908
|
});
|
|
6206
6909
|
root.add(statusBar);
|
|
6910
|
+
const menuButton = new BoxRenderable5(renderer, {
|
|
6911
|
+
position: "absolute",
|
|
6912
|
+
bottom: 1,
|
|
6913
|
+
right: 1,
|
|
6914
|
+
height: 1,
|
|
6915
|
+
paddingX: 1,
|
|
6916
|
+
backgroundColor: colors.surface,
|
|
6917
|
+
focusable: false
|
|
6918
|
+
});
|
|
6919
|
+
const menuButtonLabel = new TextRenderable5(renderer, {
|
|
6920
|
+
...NON_INTERACTIVE_TEXT,
|
|
6921
|
+
content: "\u2630 menu",
|
|
6922
|
+
fg: colors.muted,
|
|
6923
|
+
bg: colors.surface,
|
|
6924
|
+
height: 1
|
|
6925
|
+
});
|
|
6926
|
+
menuButton.add(menuButtonLabel);
|
|
6927
|
+
root.add(menuButton);
|
|
6928
|
+
menuButton.onMouseDown = (event) => {
|
|
6929
|
+
event.stopPropagation();
|
|
6930
|
+
if (currentOverlay === "leader") showOverlay("none");
|
|
6931
|
+
else showLeaderMenu();
|
|
6932
|
+
renderer.requestRender();
|
|
6933
|
+
};
|
|
6934
|
+
menuButton.onMouseOver = () => {
|
|
6935
|
+
renderer.setMousePointer("pointer");
|
|
6936
|
+
menuButtonLabel.fg = colors.accent;
|
|
6937
|
+
renderer.requestRender();
|
|
6938
|
+
};
|
|
6939
|
+
menuButton.onMouseOut = () => {
|
|
6940
|
+
renderer.setMousePointer("default");
|
|
6941
|
+
menuButtonLabel.fg = colors.muted;
|
|
6942
|
+
renderer.requestRender();
|
|
6943
|
+
};
|
|
6207
6944
|
const statusMgr = new StatusManager((text) => {
|
|
6208
6945
|
statusBar.content = ` ${text}`;
|
|
6209
6946
|
renderer.requestRender();
|
|
@@ -6522,16 +7259,13 @@ async function launchBrowser(theme_override) {
|
|
|
6522
7259
|
renderer.requestRender();
|
|
6523
7260
|
try {
|
|
6524
7261
|
const searchModule = new SearchModule(new TrendingAdapter());
|
|
6525
|
-
const response = await searchModule.search(
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
trendingSince: tabSince(trendingTab)
|
|
6533
|
-
}
|
|
6534
|
-
);
|
|
7262
|
+
const response = await searchModule.search(trendingQuery(), {
|
|
7263
|
+
limit: 25,
|
|
7264
|
+
sort: "stars",
|
|
7265
|
+
json: false,
|
|
7266
|
+
verbose: false,
|
|
7267
|
+
trendingSince: tabSince(trendingTab)
|
|
7268
|
+
});
|
|
6535
7269
|
const fetched = response.repos;
|
|
6536
7270
|
const since = tabSince(trendingTab);
|
|
6537
7271
|
trendingPeriod = since === "daily" ? "today" : since === "weekly" ? "this week" : "this month";
|
|
@@ -6671,7 +7405,10 @@ async function launchBrowser(theme_override) {
|
|
|
6671
7405
|
currentRepos = newRepos;
|
|
6672
7406
|
}
|
|
6673
7407
|
if (currentRepos.length === 0) {
|
|
6674
|
-
throw new NoResultsError(
|
|
7408
|
+
throw new NoResultsError(
|
|
7409
|
+
q,
|
|
7410
|
+
suggestRelaxations(parsed).map((r) => r.suggestion)
|
|
7411
|
+
);
|
|
6675
7412
|
} else {
|
|
6676
7413
|
resultsSelect.options = currentRepos.map((r) => ({
|
|
6677
7414
|
name: formatResultLine(r),
|
|
@@ -6797,6 +7534,158 @@ ${pack.description ?? ""}`;
|
|
|
6797
7534
|
if (repo) openUrl(repo.url);
|
|
6798
7535
|
}
|
|
6799
7536
|
});
|
|
7537
|
+
attachClickToSelect(renderer, resultsSelect, {
|
|
7538
|
+
keepFocus: true,
|
|
7539
|
+
onOpen: () => resultsSelect.selectCurrent()
|
|
7540
|
+
});
|
|
7541
|
+
function activateOverlaySelection() {
|
|
7542
|
+
switch (currentOverlay) {
|
|
7543
|
+
case "history": {
|
|
7544
|
+
const sel = historySelect.getSelectedOption();
|
|
7545
|
+
if (sel?.value) {
|
|
7546
|
+
const entry = sel.value.entry;
|
|
7547
|
+
showOverlay("none");
|
|
7548
|
+
if (!entry) return;
|
|
7549
|
+
if (entry.mode === "trending") {
|
|
7550
|
+
if (entry.tab)
|
|
7551
|
+
trendingTab = entry.tab;
|
|
7552
|
+
loadTrending();
|
|
7553
|
+
} else {
|
|
7554
|
+
searchInput.value = entry.query;
|
|
7555
|
+
showSearchMode();
|
|
7556
|
+
doSearch(entry.query);
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
return;
|
|
7560
|
+
}
|
|
7561
|
+
case "bookmarks": {
|
|
7562
|
+
const sel = bookmarksSelect.getSelectedOption();
|
|
7563
|
+
if (sel?.value)
|
|
7564
|
+
openUrl(sel.value.repo?.url ?? "");
|
|
7565
|
+
return;
|
|
7566
|
+
}
|
|
7567
|
+
case "saved": {
|
|
7568
|
+
const sel = savedSelect.getSelectedOption();
|
|
7569
|
+
if (sel?.value) {
|
|
7570
|
+
const s = sel.value;
|
|
7571
|
+
showOverlay("none");
|
|
7572
|
+
touchSavedSearch(s.name);
|
|
7573
|
+
if (s.mode === "trending") {
|
|
7574
|
+
if (s.tab) trendingTab = s.tab;
|
|
7575
|
+
loadTrending();
|
|
7576
|
+
} else {
|
|
7577
|
+
searchInput.value = s.query;
|
|
7578
|
+
showSearchMode();
|
|
7579
|
+
currentSort = s.sort;
|
|
7580
|
+
currentLimit = s.limit;
|
|
7581
|
+
setToolbar();
|
|
7582
|
+
doSearch(s.query);
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
return;
|
|
7586
|
+
}
|
|
7587
|
+
case "topics": {
|
|
7588
|
+
const sel = topicsSelect.getSelectedOption();
|
|
7589
|
+
if (sel?.value) {
|
|
7590
|
+
const topic = sel.value;
|
|
7591
|
+
showOverlay("none");
|
|
7592
|
+
searchInput.value = `topic:${topic.name ?? ""}`;
|
|
7593
|
+
showSearchMode();
|
|
7594
|
+
doSearch(`topic:${topic.name}`);
|
|
7595
|
+
}
|
|
7596
|
+
return;
|
|
7597
|
+
}
|
|
7598
|
+
case "export": {
|
|
7599
|
+
const sel = exportSelect.getSelectedOption();
|
|
7600
|
+
if (sel?.value && currentRepos.length > 0) {
|
|
7601
|
+
const format2 = sel.value;
|
|
7602
|
+
const path = exportToFile(currentRepos, format2);
|
|
7603
|
+
showOverlay("none");
|
|
7604
|
+
setStatus(`\u2713 Exported to ${path}`);
|
|
7605
|
+
} else {
|
|
7606
|
+
setStatus("No results to export");
|
|
7607
|
+
showOverlay("none");
|
|
7608
|
+
}
|
|
7609
|
+
renderer.requestRender();
|
|
7610
|
+
return;
|
|
7611
|
+
}
|
|
7612
|
+
case "share": {
|
|
7613
|
+
const sel = shareSelect.getSelectedOption();
|
|
7614
|
+
if (sel?.value) {
|
|
7615
|
+
const opt = resultsSelect.getSelectedOption();
|
|
7616
|
+
const repo = opt?.value;
|
|
7617
|
+
if (repo) {
|
|
7618
|
+
const text = formatShare(repo, sel.value);
|
|
7619
|
+
copyToClipboard(text).then((ok) => {
|
|
7620
|
+
showOverlay("none");
|
|
7621
|
+
setStatus(ok ? "\u2713 Copied!" : "Clipboard not available");
|
|
7622
|
+
});
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
renderer.requestRender();
|
|
7626
|
+
return;
|
|
7627
|
+
}
|
|
7628
|
+
case "help": {
|
|
7629
|
+
showOverlay("none");
|
|
7630
|
+
renderer.requestRender();
|
|
7631
|
+
return;
|
|
7632
|
+
}
|
|
7633
|
+
default:
|
|
7634
|
+
return;
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
function attachOverlayMouse(box, select) {
|
|
7638
|
+
attachClickToSelect(renderer, select, {
|
|
7639
|
+
onOpen: () => activateOverlaySelection(),
|
|
7640
|
+
onClickMiss: () => {
|
|
7641
|
+
showOverlay("none");
|
|
7642
|
+
renderer.requestRender();
|
|
7643
|
+
}
|
|
7644
|
+
});
|
|
7645
|
+
attachClickOutside(box, () => {
|
|
7646
|
+
showOverlay("none");
|
|
7647
|
+
renderer.requestRender();
|
|
7648
|
+
});
|
|
7649
|
+
}
|
|
7650
|
+
attachOverlayMouse(historyBox, historySelect);
|
|
7651
|
+
attachOverlayMouse(bookmarksBox, bookmarksSelect);
|
|
7652
|
+
attachOverlayMouse(savedBox, savedSelect);
|
|
7653
|
+
attachOverlayMouse(topicsBox, topicsSelect);
|
|
7654
|
+
attachOverlayMouse(exportBox, exportSelect);
|
|
7655
|
+
attachOverlayMouse(shareBox, shareSelect);
|
|
7656
|
+
for (const panel of [helpBox, orgBox, readmeBox, compareBox, notifsBox]) {
|
|
7657
|
+
attachClickOutside(panel, () => {
|
|
7658
|
+
showOverlay("none");
|
|
7659
|
+
renderer.requestRender();
|
|
7660
|
+
});
|
|
7661
|
+
}
|
|
7662
|
+
for (const scroller of [helpScroll, orgScroll, readmeScroll, updateScroll]) {
|
|
7663
|
+
scroller.onMouseScroll = (event) => {
|
|
7664
|
+
event.stopPropagation();
|
|
7665
|
+
};
|
|
7666
|
+
}
|
|
7667
|
+
attachClickToSelect(renderer, leaderSelect, {
|
|
7668
|
+
onOpen: () => {
|
|
7669
|
+
const entry = leaderSelect.getSelectedOption()?.value;
|
|
7670
|
+
if (!entry) return;
|
|
7671
|
+
if (entry.type === "category") {
|
|
7672
|
+
pushMenuLevel(` Menu > ${entry.name}`, entry.name, entry.children, 0);
|
|
7673
|
+
} else {
|
|
7674
|
+
resetMenu();
|
|
7675
|
+
showOverlay("none");
|
|
7676
|
+
entry.action();
|
|
7677
|
+
}
|
|
7678
|
+
renderer.requestRender();
|
|
7679
|
+
},
|
|
7680
|
+
onClickMiss: () => {
|
|
7681
|
+
showOverlay("none");
|
|
7682
|
+
renderer.requestRender();
|
|
7683
|
+
}
|
|
7684
|
+
});
|
|
7685
|
+
attachClickOutside(leaderDim, () => {
|
|
7686
|
+
showOverlay("none");
|
|
7687
|
+
renderer.requestRender();
|
|
7688
|
+
});
|
|
6800
7689
|
renderer.keyInput.on("keypress", (key) => {
|
|
6801
7690
|
if (tokenPromptBox.visible) {
|
|
6802
7691
|
if (key.name === "escape") {
|
|
@@ -6890,21 +7779,7 @@ ${pack.description ?? ""}`;
|
|
|
6890
7779
|
return;
|
|
6891
7780
|
}
|
|
6892
7781
|
if (key.name === "enter" || key.name === "return") {
|
|
6893
|
-
|
|
6894
|
-
if (sel?.value) {
|
|
6895
|
-
const entry = sel.value.entry;
|
|
6896
|
-
showOverlay("none");
|
|
6897
|
-
if (!entry) return;
|
|
6898
|
-
if (entry.mode === "trending") {
|
|
6899
|
-
if (entry.tab)
|
|
6900
|
-
trendingTab = entry.tab;
|
|
6901
|
-
loadTrending();
|
|
6902
|
-
} else {
|
|
6903
|
-
searchInput.value = entry.query;
|
|
6904
|
-
showSearchMode();
|
|
6905
|
-
doSearch(entry.query);
|
|
6906
|
-
}
|
|
6907
|
-
}
|
|
7782
|
+
activateOverlaySelection();
|
|
6908
7783
|
return;
|
|
6909
7784
|
}
|
|
6910
7785
|
return;
|
|
@@ -6923,9 +7798,7 @@ ${pack.description ?? ""}`;
|
|
|
6923
7798
|
return;
|
|
6924
7799
|
}
|
|
6925
7800
|
if (key.name === "enter" || key.name === "return") {
|
|
6926
|
-
|
|
6927
|
-
if (sel?.value)
|
|
6928
|
-
openUrl(sel.value.repo?.url ?? "");
|
|
7801
|
+
activateOverlaySelection();
|
|
6929
7802
|
return;
|
|
6930
7803
|
}
|
|
6931
7804
|
return;
|
|
@@ -6942,54 +7815,21 @@ ${pack.description ?? ""}`;
|
|
|
6942
7815
|
return;
|
|
6943
7816
|
}
|
|
6944
7817
|
if (key.name === "enter" || key.name === "return") {
|
|
6945
|
-
|
|
6946
|
-
if (sel?.value) {
|
|
6947
|
-
const s = sel.value;
|
|
6948
|
-
showOverlay("none");
|
|
6949
|
-
touchSavedSearch(s.name);
|
|
6950
|
-
if (s.mode === "trending") {
|
|
6951
|
-
if (s.tab) trendingTab = s.tab;
|
|
6952
|
-
loadTrending();
|
|
6953
|
-
} else {
|
|
6954
|
-
searchInput.value = s.query;
|
|
6955
|
-
showSearchMode();
|
|
6956
|
-
currentSort = s.sort;
|
|
6957
|
-
currentLimit = s.limit;
|
|
6958
|
-
setToolbar();
|
|
6959
|
-
doSearch(s.query);
|
|
6960
|
-
}
|
|
6961
|
-
}
|
|
7818
|
+
activateOverlaySelection();
|
|
6962
7819
|
return;
|
|
6963
7820
|
}
|
|
6964
7821
|
return;
|
|
6965
7822
|
}
|
|
6966
7823
|
if (currentOverlay === "topics") {
|
|
6967
7824
|
if (key.name === "enter" || key.name === "return") {
|
|
6968
|
-
|
|
6969
|
-
if (sel?.value) {
|
|
6970
|
-
const topic = sel.value;
|
|
6971
|
-
showOverlay("none");
|
|
6972
|
-
searchInput.value = `topic:${topic.name ?? ""}`;
|
|
6973
|
-
showSearchMode();
|
|
6974
|
-
doSearch(`topic:${topic.name}`);
|
|
6975
|
-
}
|
|
7825
|
+
activateOverlaySelection();
|
|
6976
7826
|
return;
|
|
6977
7827
|
}
|
|
6978
7828
|
return;
|
|
6979
7829
|
}
|
|
6980
7830
|
if (currentOverlay === "export") {
|
|
6981
7831
|
if (key.name === "enter" || key.name === "return") {
|
|
6982
|
-
|
|
6983
|
-
if (sel?.value && currentRepos.length > 0) {
|
|
6984
|
-
const format2 = sel.value;
|
|
6985
|
-
const path = exportToFile(currentRepos, format2);
|
|
6986
|
-
showOverlay("none");
|
|
6987
|
-
setStatus(`\u2713 Exported to ${path}`);
|
|
6988
|
-
} else {
|
|
6989
|
-
setStatus("No results to export");
|
|
6990
|
-
showOverlay("none");
|
|
6991
|
-
}
|
|
6992
|
-
renderer.requestRender();
|
|
7832
|
+
activateOverlaySelection();
|
|
6993
7833
|
return;
|
|
6994
7834
|
}
|
|
6995
7835
|
return;
|
|
@@ -7018,26 +7858,13 @@ ${pack.description ?? ""}`;
|
|
|
7018
7858
|
}
|
|
7019
7859
|
if (currentOverlay === "share") {
|
|
7020
7860
|
if (key.name === "enter" || key.name === "return") {
|
|
7021
|
-
|
|
7022
|
-
if (sel?.value) {
|
|
7023
|
-
const opt = resultsSelect.getSelectedOption();
|
|
7024
|
-
const repo = opt?.value;
|
|
7025
|
-
if (repo) {
|
|
7026
|
-
const text = formatShare(repo, sel.value);
|
|
7027
|
-
copyToClipboard(text).then((ok) => {
|
|
7028
|
-
showOverlay("none");
|
|
7029
|
-
setStatus(ok ? "\u2713 Copied!" : "Clipboard not available");
|
|
7030
|
-
});
|
|
7031
|
-
}
|
|
7032
|
-
}
|
|
7033
|
-
renderer.requestRender();
|
|
7861
|
+
activateOverlaySelection();
|
|
7034
7862
|
return;
|
|
7035
7863
|
}
|
|
7036
7864
|
return;
|
|
7037
7865
|
}
|
|
7038
7866
|
if (currentOverlay === "help") {
|
|
7039
|
-
|
|
7040
|
-
renderer.requestRender();
|
|
7867
|
+
activateOverlaySelection();
|
|
7041
7868
|
return;
|
|
7042
7869
|
}
|
|
7043
7870
|
if (currentOverlay === "update") {
|
|
@@ -7062,29 +7889,7 @@ ${pack.description ?? ""}`;
|
|
|
7062
7889
|
return;
|
|
7063
7890
|
}
|
|
7064
7891
|
if (key.name === "enter" || key.name === "return") {
|
|
7065
|
-
|
|
7066
|
-
if (action === "now") {
|
|
7067
|
-
showOverlay("none");
|
|
7068
|
-
recordPreUpdateState(cachedVersion ?? "");
|
|
7069
|
-
performUpdate().then((ok) => {
|
|
7070
|
-
if (ok) {
|
|
7071
|
-
setStatus("\u2713 Update complete \u2014 restart ghfind");
|
|
7072
|
-
} else {
|
|
7073
|
-
setStatus("\u2717 Update failed \u2014 check terminal output");
|
|
7074
|
-
}
|
|
7075
|
-
}).catch(() => {
|
|
7076
|
-
setStatus("\u2717 Update failed \u2014 check terminal output");
|
|
7077
|
-
});
|
|
7078
|
-
} else if (action === "never") {
|
|
7079
|
-
suppressUpdateNotices();
|
|
7080
|
-
showOverlay("none");
|
|
7081
|
-
setStatus("Update notices suppressed");
|
|
7082
|
-
} else {
|
|
7083
|
-
snoozeUpdateNotices(3);
|
|
7084
|
-
showOverlay("none");
|
|
7085
|
-
setStatus("Update reminder snoozed for 3 days");
|
|
7086
|
-
}
|
|
7087
|
-
renderer.requestRender();
|
|
7892
|
+
runUpdateOption(updateSelectedOption);
|
|
7088
7893
|
return;
|
|
7089
7894
|
}
|
|
7090
7895
|
return;
|
|
@@ -7107,8 +7912,14 @@ ${pack.description ?? ""}`;
|
|
|
7107
7912
|
toggleGraph();
|
|
7108
7913
|
return;
|
|
7109
7914
|
}
|
|
7110
|
-
if (key.name === "
|
|
7111
|
-
|
|
7915
|
+
if (key.name === "escape") {
|
|
7916
|
+
if (currentOverlay !== "none") {
|
|
7917
|
+
showOverlay("none");
|
|
7918
|
+
setToolbar();
|
|
7919
|
+
} else {
|
|
7920
|
+
showLeaderMenu();
|
|
7921
|
+
}
|
|
7922
|
+
renderer.requestRender();
|
|
7112
7923
|
return;
|
|
7113
7924
|
}
|
|
7114
7925
|
if (!searchInput.focused && errorHint) {
|
|
@@ -7663,7 +8474,7 @@ function formatPackageLines(pkgs) {
|
|
|
7663
8474
|
function asMcpToolError(err) {
|
|
7664
8475
|
return new McpToolError(err instanceof Error ? err.message : String(err));
|
|
7665
8476
|
}
|
|
7666
|
-
var TRENDING_QUERY =
|
|
8477
|
+
var TRENDING_QUERY = trendingQuery();
|
|
7667
8478
|
var REPO_SORTS = ["best-match", "stars", "updated", "forks"];
|
|
7668
8479
|
var SKILL_SOURCES = ["local", "registry"];
|
|
7669
8480
|
var TRENDING_SINCE = ["daily", "weekly", "monthly"];
|
|
@@ -7710,6 +8521,17 @@ var TOOLS = [
|
|
|
7710
8521
|
const token = resolveToken(state, args.token);
|
|
7711
8522
|
const provider = createGitHubSearch(void 0, token ? [token] : []);
|
|
7712
8523
|
const parsed = parseQuery(query);
|
|
8524
|
+
try {
|
|
8525
|
+
validateQuery(parsed);
|
|
8526
|
+
} catch (err) {
|
|
8527
|
+
if (err instanceof BadQueryError) {
|
|
8528
|
+
return {
|
|
8529
|
+
text: `Invalid query: ${err.detail}${err.hint ? ` Try: ${err.hint}` : ""}`,
|
|
8530
|
+
data: { totalCount: 0, repos: [], invalidQuery: true }
|
|
8531
|
+
};
|
|
8532
|
+
}
|
|
8533
|
+
throw err;
|
|
8534
|
+
}
|
|
7713
8535
|
const options = {
|
|
7714
8536
|
limit,
|
|
7715
8537
|
sort,
|
|
@@ -8667,6 +9489,7 @@ Options:
|
|
|
8667
9489
|
}
|
|
8668
9490
|
function buildSearchContext(flags) {
|
|
8669
9491
|
const parsed = applyFlagFilters(parseQuery(flags.query), {});
|
|
9492
|
+
validateQuery(parsed);
|
|
8670
9493
|
return {
|
|
8671
9494
|
query: flags.query,
|
|
8672
9495
|
parsed,
|
|
@@ -9098,17 +9921,14 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
9098
9921
|
console.error(err instanceof Error ? err.message : String(err));
|
|
9099
9922
|
process.exit(1);
|
|
9100
9923
|
}
|
|
9101
|
-
const response = await trendingSearch.search(
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
...language ? { trendingLanguage: language } : {}
|
|
9110
|
-
}
|
|
9111
|
-
);
|
|
9924
|
+
const response = await trendingSearch.search(trendingQuery(), {
|
|
9925
|
+
limit: 25,
|
|
9926
|
+
sort: "stars",
|
|
9927
|
+
json: false,
|
|
9928
|
+
verbose: false,
|
|
9929
|
+
trendingSince: since,
|
|
9930
|
+
...language ? { trendingLanguage: language } : {}
|
|
9931
|
+
});
|
|
9112
9932
|
if (outputFormat) {
|
|
9113
9933
|
console.log(format(response.repos, outputFormat));
|
|
9114
9934
|
} else if (flags.count) {
|