github-search-cli 9.8.2 → 9.9.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 +129 -0
- package/README.md +65 -26
- package/dist/cli.js +898 -184
- package/docs/HOW-TO.md +254 -0
- package/package.json +2 -1
- package/release-notes/9.8.3.md +98 -0
- package/release-notes/9.9.0.md +193 -0
- package/src/cli.ts +13 -11
- package/src/errors.ts +56 -5
- package/src/help.ts +11 -0
- package/src/image-loader.ts +57 -2
- package/src/markdown-images.ts +93 -4
- package/src/markdown-view.ts +49 -20
- package/src/mcp-server.ts +34 -2
- package/src/search-state.ts +8 -10
- package/src/search.ts +701 -12
- package/src/session.ts +58 -2
- package/src/trending.ts +2 -6
- package/src/tui.ts +236 -50
- package/src/types.ts +11 -1
- package/src/update-check.ts +72 -6
- package/src/watch.ts +16 -11
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,22 @@ var NetworkError = class extends SearchCliError {
|
|
|
16
16
|
this.name = "NetworkError";
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
|
+
var AuthError = class extends SearchCliError {
|
|
20
|
+
constructor() {
|
|
21
|
+
const msg = "\u26A0 Bad GitHub token (401). Press [t] to fix it, [u] to search without one";
|
|
22
|
+
super("GitHub API returned 401 (invalid or revoked token)", msg);
|
|
23
|
+
this.name = "AuthError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var ForbiddenError = class extends SearchCliError {
|
|
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;
|
|
31
|
+
this.name = "ForbiddenError";
|
|
32
|
+
}
|
|
33
|
+
detail;
|
|
34
|
+
};
|
|
19
35
|
var RateLimitError = class extends SearchCliError {
|
|
20
36
|
constructor(hasToken, resetSeconds) {
|
|
21
37
|
const msg = hasToken ? `\u26A0 API rate limit exceeded (5,000/hr). Resets in ${resetSeconds ?? "?"}s. [r]etry [c]hange token` : "\u26A0 Rate limited (60/hr). Set GITHUB_TOKEN for 5,000/hr. [r]etry";
|
|
@@ -25,16 +41,36 @@ var RateLimitError = class extends SearchCliError {
|
|
|
25
41
|
}
|
|
26
42
|
hasToken;
|
|
27
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
|
+
};
|
|
28
59
|
var NoResultsError = class extends SearchCliError {
|
|
29
|
-
|
|
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";
|
|
30
63
|
super(
|
|
31
|
-
`No results for "${query}"`,
|
|
32
|
-
`\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}`
|
|
33
67
|
);
|
|
34
68
|
this.query = query;
|
|
69
|
+
this.relaxations = relaxations;
|
|
35
70
|
this.name = "NoResultsError";
|
|
36
71
|
}
|
|
37
72
|
query;
|
|
73
|
+
relaxations;
|
|
38
74
|
};
|
|
39
75
|
var ParseError = class extends SearchCliError {
|
|
40
76
|
constructor(source, detail) {
|
|
@@ -195,9 +231,11 @@ function normalizeEnvelope(env) {
|
|
|
195
231
|
var KNOWN_QUALIFIERS = [
|
|
196
232
|
"language",
|
|
197
233
|
"stars",
|
|
234
|
+
"forks",
|
|
198
235
|
"fork",
|
|
199
236
|
"archived",
|
|
200
237
|
"topic",
|
|
238
|
+
"topics",
|
|
201
239
|
"user",
|
|
202
240
|
"org",
|
|
203
241
|
"repo",
|
|
@@ -207,8 +245,80 @@ var KNOWN_QUALIFIERS = [
|
|
|
207
245
|
"in",
|
|
208
246
|
"size",
|
|
209
247
|
"license",
|
|
210
|
-
"created"
|
|
248
|
+
"created",
|
|
249
|
+
"followers"
|
|
211
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
|
+
}
|
|
212
322
|
var QUALIFIER_SUGGESTIONS = {
|
|
213
323
|
l: ["language:"],
|
|
214
324
|
s: ["stars:", "size:"],
|
|
@@ -224,13 +334,164 @@ var QUALIFIER_SUGGESTIONS = {
|
|
|
224
334
|
a: ["archived:"]
|
|
225
335
|
};
|
|
226
336
|
function suggestFor(input) {
|
|
227
|
-
const trimmed = input.trim().toLowerCase();
|
|
337
|
+
const trimmed = input.trim().toLowerCase().replace(/:$/, "");
|
|
228
338
|
if (trimmed.length === 0) return [];
|
|
229
339
|
if (trimmed.length === 1 && QUALIFIER_SUGGESTIONS[trimmed]) {
|
|
230
340
|
return QUALIFIER_SUGGESTIONS[trimmed];
|
|
231
341
|
}
|
|
232
|
-
const
|
|
233
|
-
|
|
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;
|
|
234
495
|
}
|
|
235
496
|
function tokenize(input) {
|
|
236
497
|
const tokens = [];
|
|
@@ -258,22 +519,46 @@ function tokenize(input) {
|
|
|
258
519
|
}
|
|
259
520
|
return tokens;
|
|
260
521
|
}
|
|
522
|
+
var EMPTY_QUERY = {
|
|
523
|
+
keywords: [],
|
|
524
|
+
qualifiers: [],
|
|
525
|
+
raw: "",
|
|
526
|
+
corrections: []
|
|
527
|
+
};
|
|
528
|
+
function trendingQuery() {
|
|
529
|
+
return { ...EMPTY_QUERY, raw: "trending" };
|
|
530
|
+
}
|
|
261
531
|
function parseQuery(raw) {
|
|
262
532
|
const trimmed = raw.trim();
|
|
263
533
|
if (trimmed === "") {
|
|
264
|
-
return { keywords: [], qualifiers: [], raw };
|
|
534
|
+
return { keywords: [], qualifiers: [], raw, corrections: [] };
|
|
265
535
|
}
|
|
266
536
|
const keywords = [];
|
|
267
537
|
const qualifiers = [];
|
|
538
|
+
const corrections = [];
|
|
268
539
|
for (const token of tokenize(trimmed)) {
|
|
269
540
|
const qualifier = matchQualifier(token);
|
|
270
541
|
if (qualifier) {
|
|
271
|
-
|
|
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
|
+
});
|
|
272
557
|
} else {
|
|
273
558
|
keywords.push(token);
|
|
274
559
|
}
|
|
275
560
|
}
|
|
276
|
-
return { keywords, qualifiers, raw: trimmed };
|
|
561
|
+
return { keywords, qualifiers, raw: trimmed, corrections };
|
|
277
562
|
}
|
|
278
563
|
function matchQualifier(token) {
|
|
279
564
|
const negated = token.startsWith("-");
|
|
@@ -282,8 +567,7 @@ function matchQualifier(token) {
|
|
|
282
567
|
if (idx <= 0) return null;
|
|
283
568
|
const key = body.slice(0, idx).toLowerCase();
|
|
284
569
|
let value = body.slice(idx + 1);
|
|
285
|
-
if (
|
|
286
|
-
if (!/^[a-z]+$/i.test(key)) return null;
|
|
570
|
+
if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/i.test(key)) return null;
|
|
287
571
|
if (value.startsWith("//")) return null;
|
|
288
572
|
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
289
573
|
value = value.slice(1, -1);
|
|
@@ -316,11 +600,143 @@ function applyFlagFilters(query, flags) {
|
|
|
316
600
|
});
|
|
317
601
|
return { ...query, qualifiers };
|
|
318
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
|
+
}
|
|
319
614
|
function validateQuery(query) {
|
|
320
615
|
const has = (key) => query.qualifiers.some((q) => q.key === key && !q.negated);
|
|
321
616
|
if (has("visibility") && has("private")) {
|
|
322
|
-
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
|
+
}
|
|
323
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
|
+
}));
|
|
324
740
|
}
|
|
325
741
|
function buildGitHubQuery(query) {
|
|
326
742
|
const parts = [];
|
|
@@ -417,6 +833,11 @@ var GitHubSearchAdapter = class {
|
|
|
417
833
|
});
|
|
418
834
|
const remainingStr = res.headers.get("x-ratelimit-remaining");
|
|
419
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
|
+
}
|
|
420
841
|
if (res.status === 403) {
|
|
421
842
|
if (remainingStr === "0" || remainingStr === null) {
|
|
422
843
|
if (this.tokens.length > 0 && attempt < this.tokens.length) {
|
|
@@ -428,9 +849,25 @@ var GitHubSearchAdapter = class {
|
|
|
428
849
|
}
|
|
429
850
|
const body = await res.text().catch(() => "");
|
|
430
851
|
this.logger.error(
|
|
431
|
-
`[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)}`
|
|
432
853
|
);
|
|
433
|
-
|
|
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);
|
|
864
|
+
}
|
|
865
|
+
if (res.status === 401) {
|
|
866
|
+
const body = await res.text().catch(() => "");
|
|
867
|
+
this.logger.error(
|
|
868
|
+
`[github] auth rejected (401): ${body.slice(0, 200)}`
|
|
869
|
+
);
|
|
870
|
+
throw new AuthError();
|
|
434
871
|
}
|
|
435
872
|
if (!res.ok) {
|
|
436
873
|
const body = await res.text().catch(() => "");
|
|
@@ -467,6 +904,7 @@ var GitHubSearchAdapter = class {
|
|
|
467
904
|
lastErr = void 0;
|
|
468
905
|
break;
|
|
469
906
|
} catch (err) {
|
|
907
|
+
if (err instanceof RateLimitError) throw err;
|
|
470
908
|
lastErr = err;
|
|
471
909
|
if (rateLimited) break;
|
|
472
910
|
}
|
|
@@ -1715,7 +2153,7 @@ var cached = null;
|
|
|
1715
2153
|
function getVersion() {
|
|
1716
2154
|
if (cached) return cached;
|
|
1717
2155
|
if (true) {
|
|
1718
|
-
cached = "9.
|
|
2156
|
+
cached = "9.9.0";
|
|
1719
2157
|
return cached;
|
|
1720
2158
|
}
|
|
1721
2159
|
try {
|
|
@@ -1974,22 +2412,22 @@ async function runWatch(opts, onChange, onError) {
|
|
|
1974
2412
|
let previous = [];
|
|
1975
2413
|
let failCount = 0;
|
|
1976
2414
|
let intervalId = null;
|
|
2415
|
+
if (!opts.trending) {
|
|
2416
|
+
validateQuery(applyFlagFilters(parseQuery(opts.query), {}));
|
|
2417
|
+
}
|
|
1977
2418
|
const tick = async () => {
|
|
1978
2419
|
try {
|
|
1979
2420
|
let repos;
|
|
1980
2421
|
if (opts.trending) {
|
|
1981
2422
|
const since = opts.query === "weekly" ? "weekly" : opts.query === "monthly" ? "monthly" : "daily";
|
|
1982
2423
|
const trendingSearch = createTrendingSearch();
|
|
1983
|
-
const response = await trendingSearch.search(
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
trendingSince: since
|
|
1991
|
-
}
|
|
1992
|
-
);
|
|
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
|
+
});
|
|
1993
2431
|
repos = response.repos;
|
|
1994
2432
|
} else {
|
|
1995
2433
|
const parsed = applyFlagFilters(parseQuery(opts.query), {});
|
|
@@ -2865,6 +3303,122 @@ function comparisonMarkdown(repos) {
|
|
|
2865
3303
|
return [header, sep, ...rows].join("\n");
|
|
2866
3304
|
}
|
|
2867
3305
|
|
|
3306
|
+
// src/markdown-images.ts
|
|
3307
|
+
var MIN_IMAGE_PIXELS = 100;
|
|
3308
|
+
var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
3309
|
+
var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
|
|
3310
|
+
var LENGTH_ATTR = /\b(width|height)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
3311
|
+
var PIXEL_VALUE = /^(\d+)(?:px)?$/i;
|
|
3312
|
+
var HTML_TAG = /<\/?[a-z][^>]*>/gi;
|
|
3313
|
+
function declaredImageSize(tag) {
|
|
3314
|
+
const values = {};
|
|
3315
|
+
for (const [, name, dq, sq] of tag.matchAll(LENGTH_ATTR)) {
|
|
3316
|
+
const raw = dq ?? sq ?? "";
|
|
3317
|
+
const match = PIXEL_VALUE.exec(raw.trim());
|
|
3318
|
+
if (!match) continue;
|
|
3319
|
+
const n = Number.parseInt(match[1], 10);
|
|
3320
|
+
if (Number.isFinite(n) && n > 0) {
|
|
3321
|
+
values[name] = n;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
if (values.width === void 0 && values.height === void 0)
|
|
3325
|
+
return void 0;
|
|
3326
|
+
return {
|
|
3327
|
+
width: values.width ?? values.height ?? 0,
|
|
3328
|
+
height: values.height ?? values.width ?? 0
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
function declaresTooSmall(size) {
|
|
3332
|
+
if (!size) return false;
|
|
3333
|
+
return size.width < MIN_IMAGE_PIXELS || size.height < MIN_IMAGE_PIXELS;
|
|
3334
|
+
}
|
|
3335
|
+
function imagesFromHtml(raw) {
|
|
3336
|
+
if (!raw || !/<img\b/i.test(raw)) return [];
|
|
3337
|
+
const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
|
|
3338
|
+
if (withoutTags) return [];
|
|
3339
|
+
const refs = [];
|
|
3340
|
+
for (const match of raw.matchAll(IMG_TAG)) {
|
|
3341
|
+
const tag = match[0];
|
|
3342
|
+
const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
|
|
3343
|
+
refs.push({ href: match[1], alt, declared: declaredImageSize(tag) });
|
|
3344
|
+
}
|
|
3345
|
+
return refs;
|
|
3346
|
+
}
|
|
3347
|
+
function inlinePlainText(tokens) {
|
|
3348
|
+
if (!tokens) return "";
|
|
3349
|
+
let out = "";
|
|
3350
|
+
for (const token of tokens) {
|
|
3351
|
+
if (token.type === "image") continue;
|
|
3352
|
+
if (token.type === "html") {
|
|
3353
|
+
out += (token.raw ?? "").replace(HTML_TAG, "");
|
|
3354
|
+
continue;
|
|
3355
|
+
}
|
|
3356
|
+
if (token.type === "br") {
|
|
3357
|
+
out += "\n";
|
|
3358
|
+
continue;
|
|
3359
|
+
}
|
|
3360
|
+
if (token.text && (!token.tokens || token.tokens.length === 0)) {
|
|
3361
|
+
out += token.text;
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
if (token.tokens?.length) {
|
|
3365
|
+
out += inlinePlainText(token.tokens);
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
if (token.raw) out += token.raw.replace(HTML_TAG, "");
|
|
3369
|
+
}
|
|
3370
|
+
return out;
|
|
3371
|
+
}
|
|
3372
|
+
function extractImageRefs(token) {
|
|
3373
|
+
const images = [];
|
|
3374
|
+
let linkUrl;
|
|
3375
|
+
const collect = (inline2) => {
|
|
3376
|
+
if (inline2.type === "image") {
|
|
3377
|
+
if (inline2.href)
|
|
3378
|
+
images.push({ href: inline2.href, alt: inline2.text ?? "", linkUrl });
|
|
3379
|
+
return true;
|
|
3380
|
+
}
|
|
3381
|
+
if (inline2.type === "link" || inline2.type === "linkReference") {
|
|
3382
|
+
if (inline2.tokens?.length) {
|
|
3383
|
+
const outer = linkUrl;
|
|
3384
|
+
linkUrl = inline2.href || outer;
|
|
3385
|
+
const ok = inline2.tokens.every(collect);
|
|
3386
|
+
linkUrl = outer;
|
|
3387
|
+
return ok;
|
|
3388
|
+
}
|
|
3389
|
+
return false;
|
|
3390
|
+
}
|
|
3391
|
+
if (inline2.type === "html") {
|
|
3392
|
+
const refs = imagesFromHtml(inline2.raw);
|
|
3393
|
+
if (refs.length > 0) {
|
|
3394
|
+
images.push(...refs);
|
|
3395
|
+
return true;
|
|
3396
|
+
}
|
|
3397
|
+
return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
|
|
3398
|
+
}
|
|
3399
|
+
if (inline2.type === "text" || inline2.type === "escape") {
|
|
3400
|
+
return !(inline2.text ?? "").trim();
|
|
3401
|
+
}
|
|
3402
|
+
return false;
|
|
3403
|
+
};
|
|
3404
|
+
if (token.type === "image" && token.href) {
|
|
3405
|
+
return [{ href: token.href, alt: token.text ?? "" }];
|
|
3406
|
+
}
|
|
3407
|
+
if (token.type === "html") {
|
|
3408
|
+
const refs = imagesFromHtml(token.raw);
|
|
3409
|
+
return refs.length > 0 ? dropTooSmall(refs) : null;
|
|
3410
|
+
}
|
|
3411
|
+
const inline = token.tokens ?? [];
|
|
3412
|
+
if (inline.length === 0) return null;
|
|
3413
|
+
for (const child of inline) {
|
|
3414
|
+
if (!collect(child)) return null;
|
|
3415
|
+
}
|
|
3416
|
+
return images.length > 0 ? dropTooSmall(images) : null;
|
|
3417
|
+
}
|
|
3418
|
+
function dropTooSmall(refs) {
|
|
3419
|
+
return refs.filter((image) => !declaresTooSmall(image.declared));
|
|
3420
|
+
}
|
|
3421
|
+
|
|
2868
3422
|
// src/image-loader.ts
|
|
2869
3423
|
var MAX_IMAGE_BYTES = 6 * 1024 * 1024;
|
|
2870
3424
|
var BADGE_HOST = /(^|\.)(shields\.io|badgen\.net|badge\.fury\.io|codecov\.io|coveralls\.io|travis-ci\.(org|com)|snyk\.io|sonarcloud\.io|david-dm\.(org|io))$/i;
|
|
@@ -3032,6 +3586,23 @@ async function loadImageBytes(url, opts) {
|
|
|
3032
3586
|
function readmeBaseUrl(owner, repo, branch, path = "README.md") {
|
|
3033
3587
|
return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
|
3034
3588
|
}
|
|
3589
|
+
async function imageSizeFromBytes(bytes) {
|
|
3590
|
+
const ImageCtor = globalThis.Bun?.Image;
|
|
3591
|
+
if (!ImageCtor) return void 0;
|
|
3592
|
+
try {
|
|
3593
|
+
const meta = await new ImageCtor(bytes).metadata();
|
|
3594
|
+
if (!meta.width || !meta.height) return void 0;
|
|
3595
|
+
return { width: meta.width, height: meta.height };
|
|
3596
|
+
} catch {
|
|
3597
|
+
return void 0;
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
async function shouldRenderImage(bytes, declared) {
|
|
3601
|
+
if (declared) return !declaresTooSmall(declared);
|
|
3602
|
+
const measured = await imageSizeFromBytes(bytes);
|
|
3603
|
+
if (!measured) return true;
|
|
3604
|
+
return !declaresTooSmall(measured);
|
|
3605
|
+
}
|
|
3035
3606
|
|
|
3036
3607
|
// src/readme.ts
|
|
3037
3608
|
var CANDIDATES = [
|
|
@@ -3267,6 +3838,7 @@ import {
|
|
|
3267
3838
|
SelectRenderable,
|
|
3268
3839
|
ScrollBoxRenderable as ScrollBoxRenderable3
|
|
3269
3840
|
} from "@opentui/core";
|
|
3841
|
+
import { appendFileSync as appendFileSync2 } from "fs";
|
|
3270
3842
|
|
|
3271
3843
|
// src/trending.ts
|
|
3272
3844
|
import {
|
|
@@ -3340,6 +3912,17 @@ function buildHelpSections() {
|
|
|
3340
3912
|
{ keys: "PageUp", action: "Scroll to top of results" }
|
|
3341
3913
|
]
|
|
3342
3914
|
},
|
|
3915
|
+
{
|
|
3916
|
+
title: " Error Recovery ",
|
|
3917
|
+
titleColor: "red",
|
|
3918
|
+
rows: [
|
|
3919
|
+
{ keys: "r", action: "Retry after an error" },
|
|
3920
|
+
{ keys: "t", action: "Fix GitHub token (401/403)" },
|
|
3921
|
+
{ keys: "u", action: "Search without a token (401)" },
|
|
3922
|
+
{ keys: "c", action: "Change token (rate limit)" }
|
|
3923
|
+
],
|
|
3924
|
+
note: "Active only while the matching error is in the status bar."
|
|
3925
|
+
},
|
|
3343
3926
|
{
|
|
3344
3927
|
title: " Trending Mode ",
|
|
3345
3928
|
titleColor: "yellow",
|
|
@@ -3552,9 +4135,35 @@ function touchSavedSearch(name) {
|
|
|
3552
4135
|
|
|
3553
4136
|
// src/session.ts
|
|
3554
4137
|
var SESSION_FILE = "session.json";
|
|
4138
|
+
var MAX_QUERY_LENGTH = 200;
|
|
4139
|
+
function sanitizeQuery(query) {
|
|
4140
|
+
if (typeof query !== "string") return void 0;
|
|
4141
|
+
const trimmed = query.trim();
|
|
4142
|
+
if (trimmed.length === 0 || trimmed.length > MAX_QUERY_LENGTH) {
|
|
4143
|
+
return void 0;
|
|
4144
|
+
}
|
|
4145
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
4146
|
+
return void 0;
|
|
4147
|
+
}
|
|
4148
|
+
return trimmed;
|
|
4149
|
+
}
|
|
4150
|
+
function sanitizeSession(state) {
|
|
4151
|
+
if (!state || typeof state !== "object") return null;
|
|
4152
|
+
const mode = state.mode === "trending" || state.mode === "packages" || state.mode === "search" ? state.mode : "search";
|
|
4153
|
+
const trendingTab = typeof state.trendingTab === "string" && state.trendingTab.length > 0 ? state.trendingTab : "This Week";
|
|
4154
|
+
const limit = typeof state.limit === "number" && Number.isFinite(state.limit) ? state.limit : 50;
|
|
4155
|
+
const sort = typeof state.sort === "string" ? state.sort : "best-match";
|
|
4156
|
+
return {
|
|
4157
|
+
mode,
|
|
4158
|
+
query: sanitizeQuery(state.query) ?? "",
|
|
4159
|
+
sort,
|
|
4160
|
+
limit,
|
|
4161
|
+
trendingTab
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
3555
4164
|
function saveSession(state) {
|
|
3556
4165
|
try {
|
|
3557
|
-
writeJSON(SESSION_FILE, state);
|
|
4166
|
+
writeJSON(SESSION_FILE, sanitizeSession(state));
|
|
3558
4167
|
} catch (err) {
|
|
3559
4168
|
if (process.env.DEBUG)
|
|
3560
4169
|
console.error(
|
|
@@ -3563,7 +4172,7 @@ function saveSession(state) {
|
|
|
3563
4172
|
}
|
|
3564
4173
|
}
|
|
3565
4174
|
function restoreSession() {
|
|
3566
|
-
return readJSON(SESSION_FILE, null);
|
|
4175
|
+
return sanitizeSession(readJSON(SESSION_FILE, null));
|
|
3567
4176
|
}
|
|
3568
4177
|
|
|
3569
4178
|
// src/markdown-view.ts
|
|
@@ -3869,83 +4478,6 @@ async function rasterizeImage(bytes, opts) {
|
|
|
3869
4478
|
}
|
|
3870
4479
|
}
|
|
3871
4480
|
|
|
3872
|
-
// src/markdown-images.ts
|
|
3873
|
-
var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
|
|
3874
|
-
var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
|
|
3875
|
-
var HTML_TAG = /<\/?[a-z][^>]*>/gi;
|
|
3876
|
-
function imagesFromHtml(raw) {
|
|
3877
|
-
if (!raw || !/<img\b/i.test(raw)) return [];
|
|
3878
|
-
const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
|
|
3879
|
-
if (withoutTags) return [];
|
|
3880
|
-
const refs = [];
|
|
3881
|
-
for (const match of raw.matchAll(IMG_TAG)) {
|
|
3882
|
-
const tag = match[0];
|
|
3883
|
-
const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
|
|
3884
|
-
refs.push({ href: match[1], alt });
|
|
3885
|
-
}
|
|
3886
|
-
return refs;
|
|
3887
|
-
}
|
|
3888
|
-
function inlinePlainText(tokens) {
|
|
3889
|
-
if (!tokens) return "";
|
|
3890
|
-
let out = "";
|
|
3891
|
-
for (const token of tokens) {
|
|
3892
|
-
if (token.type === "image") continue;
|
|
3893
|
-
if (token.type === "html") {
|
|
3894
|
-
out += (token.raw ?? "").replace(HTML_TAG, "");
|
|
3895
|
-
continue;
|
|
3896
|
-
}
|
|
3897
|
-
if (token.type === "br") {
|
|
3898
|
-
out += "\n";
|
|
3899
|
-
continue;
|
|
3900
|
-
}
|
|
3901
|
-
if (token.text && (!token.tokens || token.tokens.length === 0)) {
|
|
3902
|
-
out += token.text;
|
|
3903
|
-
continue;
|
|
3904
|
-
}
|
|
3905
|
-
if (token.tokens?.length) {
|
|
3906
|
-
out += inlinePlainText(token.tokens);
|
|
3907
|
-
continue;
|
|
3908
|
-
}
|
|
3909
|
-
if (token.raw) out += token.raw.replace(HTML_TAG, "");
|
|
3910
|
-
}
|
|
3911
|
-
return out;
|
|
3912
|
-
}
|
|
3913
|
-
function extractImageRefs(token) {
|
|
3914
|
-
const images = [];
|
|
3915
|
-
const collect = (inline2) => {
|
|
3916
|
-
if (inline2.type === "image") {
|
|
3917
|
-
if (inline2.href)
|
|
3918
|
-
images.push({ href: inline2.href, alt: inline2.text ?? "" });
|
|
3919
|
-
return true;
|
|
3920
|
-
}
|
|
3921
|
-
if (inline2.type === "html") {
|
|
3922
|
-
const refs = imagesFromHtml(inline2.raw);
|
|
3923
|
-
if (refs.length > 0) {
|
|
3924
|
-
images.push(...refs);
|
|
3925
|
-
return true;
|
|
3926
|
-
}
|
|
3927
|
-
return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
|
|
3928
|
-
}
|
|
3929
|
-
if (inline2.type === "text" || inline2.type === "escape") {
|
|
3930
|
-
return !(inline2.text ?? "").trim();
|
|
3931
|
-
}
|
|
3932
|
-
return false;
|
|
3933
|
-
};
|
|
3934
|
-
if (token.type === "image" && token.href) {
|
|
3935
|
-
return [{ href: token.href, alt: token.text ?? "" }];
|
|
3936
|
-
}
|
|
3937
|
-
if (token.type === "html") {
|
|
3938
|
-
const refs = imagesFromHtml(token.raw);
|
|
3939
|
-
return refs.length > 0 ? refs : null;
|
|
3940
|
-
}
|
|
3941
|
-
const inline = token.tokens ?? [];
|
|
3942
|
-
if (inline.length === 0) return null;
|
|
3943
|
-
for (const child of inline) {
|
|
3944
|
-
if (!collect(child)) return null;
|
|
3945
|
-
}
|
|
3946
|
-
return images.length > 0 ? images : null;
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
4481
|
// src/markdown-view.ts
|
|
3950
4482
|
var MAX_IMAGE_ROWS = 24;
|
|
3951
4483
|
var RASTER_CACHE_LIMIT = 32;
|
|
@@ -3981,17 +4513,19 @@ function hexToRgb(hex) {
|
|
|
3981
4513
|
function rgba(color) {
|
|
3982
4514
|
return RGBA.fromValues(color[0], color[1], color[2], 255);
|
|
3983
4515
|
}
|
|
3984
|
-
function imageStyledText(image) {
|
|
4516
|
+
function imageStyledText(image, linkUrl) {
|
|
3985
4517
|
const chunks = [];
|
|
3986
4518
|
toImageRuns(image).forEach((runs, rowIndex) => {
|
|
3987
4519
|
if (rowIndex > 0) chunks.push({ __isChunk: true, text: "\n" });
|
|
3988
4520
|
for (const run2 of runs) {
|
|
3989
|
-
|
|
4521
|
+
const chunk = {
|
|
3990
4522
|
__isChunk: true,
|
|
3991
4523
|
text: run2.text,
|
|
3992
4524
|
fg: rgba(run2.top),
|
|
3993
4525
|
bg: rgba(run2.bottom)
|
|
3994
|
-
}
|
|
4526
|
+
};
|
|
4527
|
+
if (linkUrl) chunk.link = { url: linkUrl };
|
|
4528
|
+
chunks.push(chunk);
|
|
3995
4529
|
}
|
|
3996
4530
|
});
|
|
3997
4531
|
return new StyledText3(chunks);
|
|
@@ -4018,15 +4552,21 @@ function createMarkdownView(opts) {
|
|
|
4018
4552
|
tableOptions: { style: "grid" },
|
|
4019
4553
|
width: "100%"
|
|
4020
4554
|
});
|
|
4021
|
-
const caption = (label, color = muted()) => new TextRenderable3(renderer, {
|
|
4022
|
-
content:
|
|
4023
|
-
|
|
4555
|
+
const caption = (label, linkUrl, color = muted()) => new TextRenderable3(renderer, {
|
|
4556
|
+
content: new StyledText3([
|
|
4557
|
+
{
|
|
4558
|
+
__isChunk: true,
|
|
4559
|
+
text: ` ${label}`,
|
|
4560
|
+
fg: RGBA.fromHex(color),
|
|
4561
|
+
...linkUrl ? { link: { url: linkUrl } } : {}
|
|
4562
|
+
}
|
|
4563
|
+
]),
|
|
4024
4564
|
bg: bg(),
|
|
4025
4565
|
wrapMode: "word",
|
|
4026
4566
|
flexShrink: 0,
|
|
4027
4567
|
width: "100%"
|
|
4028
4568
|
});
|
|
4029
|
-
const rasterize = (url) => {
|
|
4569
|
+
const rasterize = (url, declared) => {
|
|
4030
4570
|
const maxCols = Math.max(8, Math.floor(opts.getImageWidth?.() ?? 100));
|
|
4031
4571
|
const key = `${maxCols}|${url}`;
|
|
4032
4572
|
const cached2 = rasterCache.get(key);
|
|
@@ -4036,6 +4576,7 @@ function createMarkdownView(opts) {
|
|
|
4036
4576
|
if (!loader) return null;
|
|
4037
4577
|
const bytes = await loader(url);
|
|
4038
4578
|
if (!bytes) return null;
|
|
4579
|
+
if (!await shouldRenderImage(bytes, declared)) return null;
|
|
4039
4580
|
return rasterizeImage(bytes, {
|
|
4040
4581
|
maxCols,
|
|
4041
4582
|
maxRows: maxImageRows,
|
|
@@ -4053,24 +4594,25 @@ function createMarkdownView(opts) {
|
|
|
4053
4594
|
const imageNode = (image) => {
|
|
4054
4595
|
const label = `\u{1F5BC} ${image.alt.trim() || "image"}`;
|
|
4055
4596
|
const url = imageBase ? resolveReadmeImageUrl(image.href, imageBase) : null;
|
|
4056
|
-
if (!imagesEnabled || !url || !opts.loadImage)
|
|
4597
|
+
if (!imagesEnabled || !url || !opts.loadImage)
|
|
4598
|
+
return caption(label, image.linkUrl);
|
|
4057
4599
|
const container = new BoxRenderable3(renderer, {
|
|
4058
4600
|
flexDirection: "column",
|
|
4059
4601
|
flexShrink: 0,
|
|
4060
4602
|
width: "100%"
|
|
4061
4603
|
});
|
|
4062
|
-
const placeholder = caption(`${label} (loading\u2026)
|
|
4604
|
+
const placeholder = caption(`${label} (loading\u2026)`, image.linkUrl);
|
|
4063
4605
|
container.add(placeholder);
|
|
4064
|
-
void rasterize(url).then(
|
|
4606
|
+
void rasterize(url, image.declared).then(
|
|
4065
4607
|
(cells) => {
|
|
4066
4608
|
if (container.isDestroyed || !container.parent) return;
|
|
4067
4609
|
container.remove(placeholder);
|
|
4068
4610
|
placeholder.destroy();
|
|
4069
|
-
container.add(caption(label));
|
|
4611
|
+
container.add(caption(label, image.linkUrl));
|
|
4070
4612
|
if (cells) {
|
|
4071
4613
|
container.add(
|
|
4072
4614
|
new TextRenderable3(renderer, {
|
|
4073
|
-
content: imageStyledText(cells),
|
|
4615
|
+
content: imageStyledText(cells, image.linkUrl),
|
|
4074
4616
|
bg: bg(),
|
|
4075
4617
|
wrapMode: "none",
|
|
4076
4618
|
selectable: false,
|
|
@@ -4112,17 +4654,22 @@ function createMarkdownView(opts) {
|
|
|
4112
4654
|
const buildRenderNode = () => (token, _context) => {
|
|
4113
4655
|
if (token.type === "heading") return headingNode(token);
|
|
4114
4656
|
const images = extractImageRefs(token);
|
|
4115
|
-
if (images
|
|
4116
|
-
|
|
4117
|
-
|
|
4657
|
+
if (images === null) return void 0;
|
|
4658
|
+
if (images.length === 0) {
|
|
4659
|
+
return new BoxRenderable3(renderer, {
|
|
4118
4660
|
flexShrink: 0,
|
|
4119
4661
|
width: "100%",
|
|
4120
|
-
|
|
4662
|
+
height: 0
|
|
4121
4663
|
});
|
|
4122
|
-
for (const image of images) box.add(imageNode(image));
|
|
4123
|
-
return box;
|
|
4124
4664
|
}
|
|
4125
|
-
|
|
4665
|
+
const box = new BoxRenderable3(renderer, {
|
|
4666
|
+
flexDirection: "column",
|
|
4667
|
+
flexShrink: 0,
|
|
4668
|
+
width: "100%",
|
|
4669
|
+
marginTop: 1
|
|
4670
|
+
});
|
|
4671
|
+
for (const image of images) box.add(imageNode(image));
|
|
4672
|
+
return box;
|
|
4126
4673
|
};
|
|
4127
4674
|
renderable.renderNode = buildRenderNode();
|
|
4128
4675
|
return {
|
|
@@ -4325,19 +4872,40 @@ function shouldCheckUpdate() {
|
|
|
4325
4872
|
if (Date.now() - state.lastCheck < CHECK_INTERVAL_MS) return false;
|
|
4326
4873
|
return true;
|
|
4327
4874
|
}
|
|
4875
|
+
function semverParts(v) {
|
|
4876
|
+
const core = v.trim().replace(/^v/i, "").split(/[-+]/)[0];
|
|
4877
|
+
return core.split(".").map((s) => {
|
|
4878
|
+
const n = Number(s);
|
|
4879
|
+
return Number.isFinite(n) ? n : 0;
|
|
4880
|
+
});
|
|
4881
|
+
}
|
|
4882
|
+
function isPrerelease(v) {
|
|
4883
|
+
const core = v.trim().replace(/^v/i, "");
|
|
4884
|
+
const plus = core.indexOf("+");
|
|
4885
|
+
const coreOnly = plus === -1 ? core : core.slice(0, plus);
|
|
4886
|
+
return coreOnly.includes("-");
|
|
4887
|
+
}
|
|
4328
4888
|
function isNewerVersion(current, latest) {
|
|
4329
4889
|
if (typeof Bun !== "undefined" && Bun.semver) {
|
|
4330
4890
|
return Bun.semver.order(current, latest) < 0;
|
|
4331
4891
|
}
|
|
4332
|
-
const curParts = current
|
|
4333
|
-
const newParts = latest
|
|
4892
|
+
const curParts = semverParts(current);
|
|
4893
|
+
const newParts = semverParts(latest);
|
|
4894
|
+
let order = 0;
|
|
4334
4895
|
for (let i = 0; i < Math.max(curParts.length, newParts.length); i++) {
|
|
4335
4896
|
const c = curParts[i] ?? 0;
|
|
4336
4897
|
const n = newParts[i] ?? 0;
|
|
4337
|
-
if (n > c)
|
|
4338
|
-
|
|
4898
|
+
if (n > c) {
|
|
4899
|
+
order = 1;
|
|
4900
|
+
break;
|
|
4901
|
+
}
|
|
4902
|
+
if (n < c) {
|
|
4903
|
+
order = -1;
|
|
4904
|
+
break;
|
|
4905
|
+
}
|
|
4339
4906
|
}
|
|
4340
|
-
return
|
|
4907
|
+
if (order !== 0) return order > 0;
|
|
4908
|
+
return isPrerelease(current) && !isPrerelease(latest);
|
|
4341
4909
|
}
|
|
4342
4910
|
async function checkForUpdate(currentVersion) {
|
|
4343
4911
|
if (process.env.DEBUG)
|
|
@@ -4444,6 +5012,17 @@ function recordPreUpdateState(currentVersion) {
|
|
|
4444
5012
|
lastInstalledVersion: currentVersion
|
|
4445
5013
|
});
|
|
4446
5014
|
}
|
|
5015
|
+
function markPostUpgradeSeen(currentVersion) {
|
|
5016
|
+
try {
|
|
5017
|
+
const state = readUpdateState();
|
|
5018
|
+
if (state.lastInstalledVersion === currentVersion) return;
|
|
5019
|
+
if (isNewerVersion(currentVersion, state.lastInstalledVersion ?? "")) {
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
writeUpdateState({ ...state, lastInstalledVersion: currentVersion });
|
|
5023
|
+
} catch {
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
4447
5026
|
function fetchReleaseNotes(version) {
|
|
4448
5027
|
try {
|
|
4449
5028
|
const notesPath = join6(PKG_DIR2, "release-notes", `${version}.md`);
|
|
@@ -4518,16 +5097,33 @@ var colors = {
|
|
|
4518
5097
|
surfaceDim: "#1a1919",
|
|
4519
5098
|
borderAccent: "#1a1919"
|
|
4520
5099
|
};
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
}
|
|
4530
|
-
|
|
5100
|
+
function createTuiLogger() {
|
|
5101
|
+
const target = process.env.GHFIND_LOG;
|
|
5102
|
+
if (!target) {
|
|
5103
|
+
return { debug: () => {
|
|
5104
|
+
}, info: () => {
|
|
5105
|
+
}, warn: () => {
|
|
5106
|
+
}, error: () => {
|
|
5107
|
+
} };
|
|
5108
|
+
}
|
|
5109
|
+
const write = (level, msg) => {
|
|
5110
|
+
try {
|
|
5111
|
+
appendFileSync2(
|
|
5112
|
+
target,
|
|
5113
|
+
`[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${msg}
|
|
5114
|
+
`
|
|
5115
|
+
);
|
|
5116
|
+
} catch {
|
|
5117
|
+
}
|
|
5118
|
+
};
|
|
5119
|
+
return {
|
|
5120
|
+
debug: (m) => write("debug", m),
|
|
5121
|
+
info: (m) => write("info", m),
|
|
5122
|
+
warn: (m) => write("warn", m),
|
|
5123
|
+
error: (m) => write("error", m)
|
|
5124
|
+
};
|
|
5125
|
+
}
|
|
5126
|
+
var logger = createTuiLogger();
|
|
4531
5127
|
var SORT_MODES = [
|
|
4532
5128
|
{ key: "best-match", label: "best-match" },
|
|
4533
5129
|
{ key: "stars", label: "stars" },
|
|
@@ -4572,7 +5168,7 @@ async function launchBrowser(theme_override) {
|
|
|
4572
5168
|
const root = renderer.root;
|
|
4573
5169
|
root.flexDirection = "column";
|
|
4574
5170
|
const config = loadConfig();
|
|
4575
|
-
|
|
5171
|
+
let githubToken = config.githubToken || process.env.GITHUB_TOKEN;
|
|
4576
5172
|
const theme = loadTheme(theme_override || config.theme);
|
|
4577
5173
|
Object.assign(colors, theme);
|
|
4578
5174
|
if (terminalBg) {
|
|
@@ -4755,6 +5351,10 @@ async function launchBrowser(theme_override) {
|
|
|
4755
5351
|
currentMode = "search";
|
|
4756
5352
|
showLanding(false);
|
|
4757
5353
|
showSearchMode();
|
|
5354
|
+
if (currentQueryInput) {
|
|
5355
|
+
searchInput.value = currentQueryInput;
|
|
5356
|
+
renderer.requestRender();
|
|
5357
|
+
}
|
|
4758
5358
|
}
|
|
4759
5359
|
},
|
|
4760
5360
|
{
|
|
@@ -5792,6 +6392,7 @@ async function launchBrowser(theme_override) {
|
|
|
5792
6392
|
if (!state.lastInstalledVersion) return;
|
|
5793
6393
|
if (state.lastInstalledVersion === currentVersion) return;
|
|
5794
6394
|
const notes = fetchReleaseNotes(currentVersion);
|
|
6395
|
+
markPostUpgradeSeen(currentVersion);
|
|
5795
6396
|
updateSelectedOption = 0;
|
|
5796
6397
|
updateHeader.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
|
|
5797
6398
|
`;
|
|
@@ -6079,9 +6680,85 @@ async function launchBrowser(theme_override) {
|
|
|
6079
6680
|
setStatus(nextTip());
|
|
6080
6681
|
}
|
|
6081
6682
|
}, 2e3);
|
|
6683
|
+
const tokenPromptBox = new BoxRenderable5(renderer, {
|
|
6684
|
+
visible: false,
|
|
6685
|
+
position: "absolute",
|
|
6686
|
+
top: 0,
|
|
6687
|
+
left: 0,
|
|
6688
|
+
width: "100%",
|
|
6689
|
+
flexDirection: "row",
|
|
6690
|
+
paddingY: 1,
|
|
6691
|
+
paddingLeft: 1,
|
|
6692
|
+
backgroundColor: colors.surface
|
|
6693
|
+
});
|
|
6694
|
+
const tokenPromptLabel = new TextRenderable5(renderer, {
|
|
6695
|
+
content: " GitHub token:",
|
|
6696
|
+
fg: colors.accent,
|
|
6697
|
+
height: 1
|
|
6698
|
+
});
|
|
6699
|
+
const tokenPromptInput = new InputRenderable(renderer, {
|
|
6700
|
+
placeholder: "paste token, Enter to save \u2014 Esc cancels",
|
|
6701
|
+
value: "",
|
|
6702
|
+
backgroundColor: colors.surface,
|
|
6703
|
+
textColor: colors.text,
|
|
6704
|
+
flexGrow: 1
|
|
6705
|
+
});
|
|
6706
|
+
tokenPromptBox.add(tokenPromptLabel);
|
|
6707
|
+
tokenPromptBox.add(tokenPromptInput);
|
|
6708
|
+
root.add(tokenPromptBox);
|
|
6709
|
+
let tokenPromptQuery = null;
|
|
6710
|
+
function showTokenPrompt(query) {
|
|
6711
|
+
tokenPromptQuery = query;
|
|
6712
|
+
tokenPromptInput.value = "";
|
|
6713
|
+
tokenPromptBox.visible = true;
|
|
6714
|
+
searchBox.visible = false;
|
|
6715
|
+
tokenPromptInput.focus();
|
|
6716
|
+
renderer.requestRender();
|
|
6717
|
+
}
|
|
6718
|
+
function hideTokenPrompt() {
|
|
6719
|
+
tokenPromptBox.visible = false;
|
|
6720
|
+
tokenPromptQuery = null;
|
|
6721
|
+
if (currentMode === "search" || currentMode === "packages") {
|
|
6722
|
+
searchBox.visible = true;
|
|
6723
|
+
searchInput.focus();
|
|
6724
|
+
}
|
|
6725
|
+
renderer.requestRender();
|
|
6726
|
+
}
|
|
6727
|
+
function persistToken2(token) {
|
|
6728
|
+
githubToken = token;
|
|
6729
|
+
config.githubToken = token;
|
|
6730
|
+
saveConfig(config);
|
|
6731
|
+
}
|
|
6732
|
+
tokenPromptInput.on("enter", () => {
|
|
6733
|
+
const token = tokenPromptInput.value.trim();
|
|
6734
|
+
const query = tokenPromptQuery;
|
|
6735
|
+
hideTokenPrompt();
|
|
6736
|
+
if (!token || !query) return;
|
|
6737
|
+
persistToken2(token);
|
|
6738
|
+
setStatus("Token saved");
|
|
6739
|
+
doSearch(query);
|
|
6740
|
+
});
|
|
6741
|
+
function fixTokenAction() {
|
|
6742
|
+
showTokenPrompt(currentQueryInput || "stars:>10000");
|
|
6743
|
+
}
|
|
6744
|
+
function unsetTokenAction() {
|
|
6745
|
+
githubToken = void 0;
|
|
6746
|
+
if (config.githubToken) {
|
|
6747
|
+
delete config.githubToken;
|
|
6748
|
+
saveConfig(config);
|
|
6749
|
+
}
|
|
6750
|
+
setStatus("Token cleared \u2014 searching without one");
|
|
6751
|
+
if (currentQueryInput) doSearch(currentQueryInput);
|
|
6752
|
+
}
|
|
6082
6753
|
function setStatus(msg) {
|
|
6083
6754
|
statusMgr.set("idle", msg);
|
|
6084
6755
|
}
|
|
6756
|
+
let errorHint = null;
|
|
6757
|
+
function reportError2(err) {
|
|
6758
|
+
errorHint = err instanceof AuthError ? "auth" : err instanceof ForbiddenError ? "forbidden" : err instanceof RateLimitError ? "ratelimit" : err instanceof NetworkError ? "network" : null;
|
|
6759
|
+
const msg = err instanceof SearchCliError ? err.userMessage : err instanceof Error ? err.message : String(err);
|
|
6760
|
+
statusMgr.set("error", msg.slice(0, 60));
|
|
6761
|
+
}
|
|
6085
6762
|
function buildSessionState() {
|
|
6086
6763
|
const mode = currentMode === "trending" ? "trending" : currentMode === "packages" ? "packages" : "search";
|
|
6087
6764
|
return {
|
|
@@ -6298,6 +6975,7 @@ async function launchBrowser(theme_override) {
|
|
|
6298
6975
|
async function loadTrending() {
|
|
6299
6976
|
currentMode = "trending";
|
|
6300
6977
|
isLoading = true;
|
|
6978
|
+
errorHint = null;
|
|
6301
6979
|
searchBox.visible = false;
|
|
6302
6980
|
toolbarText.visible = false;
|
|
6303
6981
|
trendingTabBox.visible = true;
|
|
@@ -6311,16 +6989,13 @@ async function launchBrowser(theme_override) {
|
|
|
6311
6989
|
renderer.requestRender();
|
|
6312
6990
|
try {
|
|
6313
6991
|
const searchModule = new SearchModule(new TrendingAdapter());
|
|
6314
|
-
const response = await searchModule.search(
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
trendingSince: tabSince(trendingTab)
|
|
6322
|
-
}
|
|
6323
|
-
);
|
|
6992
|
+
const response = await searchModule.search(trendingQuery(), {
|
|
6993
|
+
limit: 25,
|
|
6994
|
+
sort: "stars",
|
|
6995
|
+
json: false,
|
|
6996
|
+
verbose: false,
|
|
6997
|
+
trendingSince: tabSince(trendingTab)
|
|
6998
|
+
});
|
|
6324
6999
|
const fetched = response.repos;
|
|
6325
7000
|
const since = tabSince(trendingTab);
|
|
6326
7001
|
trendingPeriod = since === "daily" ? "today" : since === "weekly" ? "this week" : "this month";
|
|
@@ -6347,11 +7022,10 @@ async function launchBrowser(theme_override) {
|
|
|
6347
7022
|
resultCount: fetched.length
|
|
6348
7023
|
});
|
|
6349
7024
|
} catch (err) {
|
|
6350
|
-
const msg = err instanceof SearchCliError ? err.userMessage : err instanceof Error ? err.message : String(err);
|
|
6351
7025
|
resultsSelect.options = [
|
|
6352
7026
|
{ name: " (error)", description: "", value: null }
|
|
6353
7027
|
];
|
|
6354
|
-
|
|
7028
|
+
reportError2(err);
|
|
6355
7029
|
}
|
|
6356
7030
|
isLoading = false;
|
|
6357
7031
|
renderer.requestRender();
|
|
@@ -6390,6 +7064,7 @@ async function launchBrowser(theme_override) {
|
|
|
6390
7064
|
if (q === "" || isLoading) return;
|
|
6391
7065
|
currentQueryInput = q;
|
|
6392
7066
|
isLoading = true;
|
|
7067
|
+
errorHint = null;
|
|
6393
7068
|
resultsSelect.options = [
|
|
6394
7069
|
{ name: " Searching packages...", description: "", value: null }
|
|
6395
7070
|
];
|
|
@@ -6427,6 +7102,7 @@ async function launchBrowser(theme_override) {
|
|
|
6427
7102
|
async function doSearch(queryText, append = false) {
|
|
6428
7103
|
const q = queryText.trim();
|
|
6429
7104
|
if (q === "" || isLoading) return;
|
|
7105
|
+
errorHint = null;
|
|
6430
7106
|
if (!append) currentPage = 1;
|
|
6431
7107
|
deepDiveActive = false;
|
|
6432
7108
|
isLoading = true;
|
|
@@ -6459,7 +7135,10 @@ async function launchBrowser(theme_override) {
|
|
|
6459
7135
|
currentRepos = newRepos;
|
|
6460
7136
|
}
|
|
6461
7137
|
if (currentRepos.length === 0) {
|
|
6462
|
-
throw new NoResultsError(
|
|
7138
|
+
throw new NoResultsError(
|
|
7139
|
+
q,
|
|
7140
|
+
suggestRelaxations(parsed).map((r) => r.suggestion)
|
|
7141
|
+
);
|
|
6463
7142
|
} else {
|
|
6464
7143
|
resultsSelect.options = currentRepos.map((r) => ({
|
|
6465
7144
|
name: formatResultLine(r),
|
|
@@ -6489,8 +7168,7 @@ async function launchBrowser(theme_override) {
|
|
|
6489
7168
|
{ name: " (error)", description: "", value: null }
|
|
6490
7169
|
];
|
|
6491
7170
|
}
|
|
6492
|
-
|
|
6493
|
-
statusMgr.set("error", msg.slice(0, 60));
|
|
7171
|
+
reportError2(err);
|
|
6494
7172
|
}
|
|
6495
7173
|
isLoading = false;
|
|
6496
7174
|
renderer.requestRender();
|
|
@@ -6587,6 +7265,16 @@ ${pack.description ?? ""}`;
|
|
|
6587
7265
|
}
|
|
6588
7266
|
});
|
|
6589
7267
|
renderer.keyInput.on("keypress", (key) => {
|
|
7268
|
+
if (tokenPromptBox.visible) {
|
|
7269
|
+
if (key.name === "escape") {
|
|
7270
|
+
hideTokenPrompt();
|
|
7271
|
+
setStatus("Token entry cancelled");
|
|
7272
|
+
renderer.requestRender();
|
|
7273
|
+
return;
|
|
7274
|
+
}
|
|
7275
|
+
if (key.name === "enter" || key.name === "return") return;
|
|
7276
|
+
return;
|
|
7277
|
+
}
|
|
6590
7278
|
if (currentOverlay !== "none") {
|
|
6591
7279
|
if (key.name === "escape") {
|
|
6592
7280
|
if (currentOverlay === "leader") {
|
|
@@ -6890,6 +7578,27 @@ ${pack.description ?? ""}`;
|
|
|
6890
7578
|
showLeaderMenu();
|
|
6891
7579
|
return;
|
|
6892
7580
|
}
|
|
7581
|
+
if (!searchInput.focused && errorHint) {
|
|
7582
|
+
if (key.name === "r") {
|
|
7583
|
+
errorHint = null;
|
|
7584
|
+
if (currentMode === "trending") loadTrending();
|
|
7585
|
+
else if (currentQueryInput) doSearch(currentQueryInput);
|
|
7586
|
+
renderer.requestRender();
|
|
7587
|
+
return;
|
|
7588
|
+
}
|
|
7589
|
+
if (key.name === "t" && (errorHint === "auth" || errorHint === "forbidden")) {
|
|
7590
|
+
fixTokenAction();
|
|
7591
|
+
return;
|
|
7592
|
+
}
|
|
7593
|
+
if (key.name === "u" && errorHint === "auth") {
|
|
7594
|
+
unsetTokenAction();
|
|
7595
|
+
return;
|
|
7596
|
+
}
|
|
7597
|
+
if (key.name === "c" && errorHint === "ratelimit") {
|
|
7598
|
+
fixTokenAction();
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7601
|
+
}
|
|
6893
7602
|
if (key.name === "t" && !searchInput.focused) {
|
|
6894
7603
|
const current = config.theme;
|
|
6895
7604
|
const themes = listThemes();
|
|
@@ -6984,25 +7693,10 @@ ${pack.description ?? ""}`;
|
|
|
6984
7693
|
}
|
|
6985
7694
|
});
|
|
6986
7695
|
const _currentVersion = getVersion();
|
|
6987
|
-
|
|
6988
|
-
showLanding(true);
|
|
6989
|
-
}
|
|
7696
|
+
showLanding(true);
|
|
6990
7697
|
checkForUpdateAndShow();
|
|
6991
7698
|
checkPostUpgradePanel();
|
|
6992
7699
|
renderer.start();
|
|
6993
|
-
if (session?.mode) {
|
|
6994
|
-
if (currentMode === "trending") {
|
|
6995
|
-
loadTrending();
|
|
6996
|
-
} else {
|
|
6997
|
-
searchInput.value = currentQueryInput;
|
|
6998
|
-
if (currentQueryInput) {
|
|
6999
|
-
doSearch(currentQueryInput);
|
|
7000
|
-
} else {
|
|
7001
|
-
showSearchMode();
|
|
7002
|
-
}
|
|
7003
|
-
searchInput.focus();
|
|
7004
|
-
}
|
|
7005
|
-
}
|
|
7006
7700
|
renderer.requestRender();
|
|
7007
7701
|
}
|
|
7008
7702
|
function formatResultLine(repo) {
|
|
@@ -7436,7 +8130,7 @@ function formatPackageLines(pkgs) {
|
|
|
7436
8130
|
function asMcpToolError(err) {
|
|
7437
8131
|
return new McpToolError(err instanceof Error ? err.message : String(err));
|
|
7438
8132
|
}
|
|
7439
|
-
var TRENDING_QUERY =
|
|
8133
|
+
var TRENDING_QUERY = trendingQuery();
|
|
7440
8134
|
var REPO_SORTS = ["best-match", "stars", "updated", "forks"];
|
|
7441
8135
|
var SKILL_SOURCES = ["local", "registry"];
|
|
7442
8136
|
var TRENDING_SINCE = ["daily", "weekly", "monthly"];
|
|
@@ -7483,6 +8177,17 @@ var TOOLS = [
|
|
|
7483
8177
|
const token = resolveToken(state, args.token);
|
|
7484
8178
|
const provider = createGitHubSearch(void 0, token ? [token] : []);
|
|
7485
8179
|
const parsed = parseQuery(query);
|
|
8180
|
+
try {
|
|
8181
|
+
validateQuery(parsed);
|
|
8182
|
+
} catch (err) {
|
|
8183
|
+
if (err instanceof BadQueryError) {
|
|
8184
|
+
return {
|
|
8185
|
+
text: `Invalid query: ${err.detail}${err.hint ? ` Try: ${err.hint}` : ""}`,
|
|
8186
|
+
data: { totalCount: 0, repos: [], invalidQuery: true }
|
|
8187
|
+
};
|
|
8188
|
+
}
|
|
8189
|
+
throw err;
|
|
8190
|
+
}
|
|
7486
8191
|
const options = {
|
|
7487
8192
|
limit,
|
|
7488
8193
|
sort,
|
|
@@ -7490,7 +8195,18 @@ var TOOLS = [
|
|
|
7490
8195
|
verbose: false,
|
|
7491
8196
|
token
|
|
7492
8197
|
};
|
|
7493
|
-
|
|
8198
|
+
let response;
|
|
8199
|
+
try {
|
|
8200
|
+
response = await provider.search(parsed, options);
|
|
8201
|
+
} catch (err) {
|
|
8202
|
+
if (err instanceof RateLimitError) {
|
|
8203
|
+
return {
|
|
8204
|
+
text: `GitHub API rate limit exceeded for "${query}". Set a token (GITHUB_TOKEN or the tool's token argument) to raise the limit from 60/hr to 5,000/hr.`,
|
|
8205
|
+
data: { totalCount: 0, repos: [], rateLimited: true }
|
|
8206
|
+
};
|
|
8207
|
+
}
|
|
8208
|
+
throw err;
|
|
8209
|
+
}
|
|
7494
8210
|
const repos = rankRepos(response.repos, sort);
|
|
7495
8211
|
try {
|
|
7496
8212
|
appendHistory({
|
|
@@ -8429,6 +9145,7 @@ Options:
|
|
|
8429
9145
|
}
|
|
8430
9146
|
function buildSearchContext(flags) {
|
|
8431
9147
|
const parsed = applyFlagFilters(parseQuery(flags.query), {});
|
|
9148
|
+
validateQuery(parsed);
|
|
8432
9149
|
return {
|
|
8433
9150
|
query: flags.query,
|
|
8434
9151
|
parsed,
|
|
@@ -8860,17 +9577,14 @@ async function runNonInteractive(flags, outputFormat) {
|
|
|
8860
9577
|
console.error(err instanceof Error ? err.message : String(err));
|
|
8861
9578
|
process.exit(1);
|
|
8862
9579
|
}
|
|
8863
|
-
const response = await trendingSearch.search(
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
...language ? { trendingLanguage: language } : {}
|
|
8872
|
-
}
|
|
8873
|
-
);
|
|
9580
|
+
const response = await trendingSearch.search(trendingQuery(), {
|
|
9581
|
+
limit: 25,
|
|
9582
|
+
sort: "stars",
|
|
9583
|
+
json: false,
|
|
9584
|
+
verbose: false,
|
|
9585
|
+
trendingSince: since,
|
|
9586
|
+
...language ? { trendingLanguage: language } : {}
|
|
9587
|
+
});
|
|
8874
9588
|
if (outputFormat) {
|
|
8875
9589
|
console.log(format(response.repos, outputFormat));
|
|
8876
9590
|
} else if (flags.count) {
|