github-search-cli 9.7.0 → 9.7.1

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/dist/cli.js CHANGED
@@ -62,14 +62,14 @@ function flattenHtml(html) {
62
62
  }
63
63
  function parseRepoLink(block) {
64
64
  const anchor = block.match(
65
- /<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([^\"?#]+)"[^>]*class="Link"/
65
+ /<a[^>]*?href="\/([A-Za-z0-9._-]+)\/([^"?#]+)"[^>]*class="Link"/
66
66
  );
67
67
  if (!anchor) return void 0;
68
68
  return { owner: anchor[1], name: anchor[2] };
69
69
  }
70
70
  function parseStars(block) {
71
71
  const starsMatch = block.match(
72
- /href="\/[^\/\"]+\/[^\/\"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/
72
+ /href="\/[^/"]+\/[^/"]+\/stargazers"[^>]*>.*?<\/svg>\s*(\d[\d,]*)/
73
73
  );
74
74
  return starsMatch ? parseInt(starsMatch[1].replace(/,/g, ""), 10) : 0;
75
75
  }
@@ -91,9 +91,8 @@ function parseTrendingHtml(html) {
91
91
  const flat = flattenHtml(html);
92
92
  const repos = [];
93
93
  const articleRe = /<article class="Box-row">(.*?)<\/article>/gi;
94
- let match;
95
94
  let rank = 0;
96
- while ((match = articleRe.exec(flat)) !== null) {
95
+ for (const match of flat.matchAll(articleRe)) {
97
96
  rank++;
98
97
  const block = match[1];
99
98
  const link = parseRepoLink(block);
@@ -240,7 +239,7 @@ function suggestFor(input) {
240
239
  return QUALIFIER_SUGGESTIONS[trimmed];
241
240
  }
242
241
  const matches = KNOWN_QUALIFIERS.filter((k) => k.startsWith(trimmed));
243
- return matches.map((k) => k + ":");
242
+ return matches.map((k) => `${k}:`);
244
243
  }
245
244
  function tokenize(input) {
246
245
  const tokens = [];
@@ -353,7 +352,6 @@ function githubSortParam(sort) {
353
352
  return { sort: "forks", order: "desc" };
354
353
  case "updated":
355
354
  return { sort: "updated", order: "desc" };
356
- case "best-match":
357
355
  default:
358
356
  return {};
359
357
  }
@@ -378,7 +376,6 @@ function rankRepos(repos, strategy) {
378
376
  case "updated":
379
377
  copy.sort(byKey((r) => Date.parse(r.updatedAt)));
380
378
  break;
381
- case "best-match":
382
379
  default:
383
380
  break;
384
381
  }
@@ -585,6 +582,7 @@ function trendingRepoToRepo(r) {
585
582
  }
586
583
  var TrendingAdapter = class {
587
584
  name = "trending";
585
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: part of the adapter constructor contract — callers pass a shared logger (search.ts:757) even though this adapter currently logs nothing itself.
588
586
  logger;
589
587
  constructor(logger2 = noopLogger) {
590
588
  this.logger = logger2;
@@ -740,12 +738,17 @@ import {
740
738
  } from "@opentui/core";
741
739
 
742
740
  // src/open-url.ts
741
+ import { spawn } from "child_process";
743
742
  function openUrl(url) {
744
743
  try {
745
744
  const p = process.platform;
746
- if (p === "win32") Bun.spawn(["cmd", "/c", "start", "", url]);
747
- else if (p === "darwin") Bun.spawn(["open", url]);
748
- else Bun.spawn(["xdg-open", url]);
745
+ const cmd = p === "win32" ? "explorer" : p === "darwin" ? "open" : "xdg-open";
746
+ if (typeof Bun !== "undefined") {
747
+ Bun.spawn([cmd, url]);
748
+ return;
749
+ }
750
+ const child = spawn(cmd, [url], { detached: true, stdio: "ignore" });
751
+ child.unref();
749
752
  } catch {
750
753
  }
751
754
  }
@@ -935,7 +938,7 @@ function formatPackageLine(pack) {
935
938
  const nameStr = `${pack.name}@${pack.version}`.padEnd(28).slice(0, 28);
936
939
  const dlStr = `\u2193 ${fmtCount(pack.downloads)}`.padEnd(20).slice(0, 20);
937
940
  const scoreStr = `score ${pack.score.toFixed(2)}`.padEnd(12).slice(0, 12);
938
- const desc = (pack.description ?? "").length > 60 ? (pack.description ?? "").slice(0, 57) + "..." : pack.description ?? "";
941
+ const desc = (pack.description ?? "").length > 60 ? `${(pack.description ?? "").slice(0, 57)}...` : pack.description ?? "";
939
942
  const line1 = t`${bold(fg(C.nameText)(nameStr))}${fg(C.cyan)(dlStr)}${fg(C.gold)(scoreStr)}`;
940
943
  const line2 = t`${dim(fg(C.descText)(` ${desc}`))}`;
941
944
  const newline = { text: "\n", __isChunk: true };
@@ -1198,9 +1201,13 @@ var results = [];
1198
1201
  function add(status, label, detail) {
1199
1202
  results.push({ status, label, detail });
1200
1203
  }
1201
- function run(cmd, args) {
1204
+ function run(cmd, args, opts = {}) {
1202
1205
  try {
1203
- const res = spawnSync(cmd, args, { encoding: "utf8", timeout: 15e3 });
1206
+ const res = spawnSync(cmd, args, {
1207
+ encoding: "utf8",
1208
+ timeout: 15e3,
1209
+ ...opts
1210
+ });
1204
1211
  return {
1205
1212
  status: res.status,
1206
1213
  stdout: res.stdout ?? "",
@@ -1375,6 +1382,97 @@ function checkDirs() {
1375
1382
  }
1376
1383
  }
1377
1384
  }
1385
+ var PKG_NAME = "github-search-cli";
1386
+ var BIN_NAME = "ghfind";
1387
+ function runningVersion() {
1388
+ try {
1389
+ const pkg = JSON.parse(
1390
+ readFileSync(join(PKG_DIR, "package.json"), "utf8")
1391
+ );
1392
+ return pkg.version ?? "";
1393
+ } catch {
1394
+ return "";
1395
+ }
1396
+ }
1397
+ function installVersionAt(shimPath) {
1398
+ const dir = dirname(shimPath);
1399
+ for (const ancestor of [dir, dirname(dir)]) {
1400
+ for (const sub of ["node_modules", join("lib", "node_modules")]) {
1401
+ const pkgJson = join(ancestor, sub, PKG_NAME, "package.json");
1402
+ if (existsSync(pkgJson)) {
1403
+ try {
1404
+ const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
1405
+ return { version: pkg.version ?? "", dir: dirname(pkgJson) };
1406
+ } catch {
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+ const res = run(shimPath, ["--version"], {
1412
+ shell: platform() === "win32",
1413
+ timeout: 1e4
1414
+ });
1415
+ const m = /\d+\.\d+\.\d+/.exec(res.stdout);
1416
+ return m ? { version: m[0], dir } : null;
1417
+ }
1418
+ function checkInstallShadowing() {
1419
+ const isWin = platform() === "win32";
1420
+ const res = isWin ? run("where", [BIN_NAME]) : run("which", ["-a", BIN_NAME]);
1421
+ const shims = [
1422
+ ...new Set(
1423
+ res.stdout.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0)
1424
+ )
1425
+ ];
1426
+ if (shims.length === 0) {
1427
+ add("WARN", "Global installs", `${BIN_NAME} not found on PATH.`);
1428
+ return;
1429
+ }
1430
+ const running = runningVersion();
1431
+ const norm = (p) => isWin ? p.replace(/\\/g, "/").toLowerCase() : p;
1432
+ const seenDirs = /* @__PURE__ */ new Set();
1433
+ const stale = [];
1434
+ let knownCount = 0;
1435
+ let staleVersion = "";
1436
+ let staleDir = "";
1437
+ for (const shim of shims) {
1438
+ const info = installVersionAt(shim);
1439
+ const dirKey = norm(dirname(shim));
1440
+ if (seenDirs.has(dirKey)) continue;
1441
+ seenDirs.add(dirKey);
1442
+ knownCount++;
1443
+ const isRunningInstall = info !== null && norm(info.dir) === norm(PKG_DIR);
1444
+ if (!isRunningInstall && info !== null && running && info.version !== running) {
1445
+ stale.push(`${dirname(shim)} (v${info.version} \u2260 running v${running})`);
1446
+ staleVersion = info.version;
1447
+ staleDir = dirname(shim);
1448
+ }
1449
+ }
1450
+ if (stale.length > 0 && knownCount > 1) {
1451
+ add(
1452
+ "WARN",
1453
+ "Global installs",
1454
+ `${knownCount} install(s) on PATH; stale copies shadowing updates \u2192 ${stale.join("; ")}. Remove with: npm uninstall -g ${PKG_NAME} --prefix <dir>`
1455
+ );
1456
+ } else if (stale.length > 0) {
1457
+ add(
1458
+ "PASS",
1459
+ "Global installs",
1460
+ `v${staleVersion} at ${staleDir} (running v${running || "?"} from source/build) \u2014 single install, nothing shadowed.`
1461
+ );
1462
+ } else if (knownCount > 1) {
1463
+ add(
1464
+ "PASS",
1465
+ "Global installs",
1466
+ `${knownCount} install(s) on PATH, all v${running || "?"}.`
1467
+ );
1468
+ } else {
1469
+ add(
1470
+ "PASS",
1471
+ "Global installs",
1472
+ `v${running || "?"} at ${dirname(shims[0])}.`
1473
+ );
1474
+ }
1475
+ }
1378
1476
  function printReport() {
1379
1477
  const noColor = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
1380
1478
  const colors2 = (s, n) => {
@@ -1417,6 +1515,7 @@ async function runDoctor() {
1417
1515
  checkConfig();
1418
1516
  checkGithubToken();
1419
1517
  checkDirs();
1518
+ checkInstallShadowing();
1420
1519
  printReport();
1421
1520
  const hasFail = results.some((r) => r.status === "FAIL");
1422
1521
  process.exitCode = hasFail ? 1 : 0;
@@ -1429,7 +1528,7 @@ import {
1429
1528
  version as nodeVersion,
1430
1529
  versions as nodeVersions
1431
1530
  } from "process";
1432
- import { spawn } from "child_process";
1531
+ import { spawn as spawn2 } from "child_process";
1433
1532
 
1434
1533
  // src/version.ts
1435
1534
  import { readFileSync as readFileSync2 } from "fs";
@@ -1439,7 +1538,7 @@ var cached = null;
1439
1538
  function getVersion() {
1440
1539
  if (cached) return cached;
1441
1540
  if (true) {
1442
- cached = "9.7.0";
1541
+ cached = "9.7.1";
1443
1542
  return cached;
1444
1543
  }
1445
1544
  try {
@@ -1515,14 +1614,15 @@ function tryOpenUrl(url) {
1515
1614
  try {
1516
1615
  const p = osPlatform;
1517
1616
  if (typeof Bun !== "undefined") {
1518
- if (p === "win32") Bun.spawn(["cmd", "/c", "start", "", url]);
1519
- else if (p === "darwin") Bun.spawn(["open", url]);
1617
+ if (p === "win32") {
1618
+ Bun.spawn(["explorer", url]);
1619
+ } else if (p === "darwin") Bun.spawn(["open", url]);
1520
1620
  else Bun.spawn(["xdg-open", url]);
1521
1621
  return true;
1522
1622
  }
1523
- const cmd = p === "win32" ? "cmd" : p === "darwin" ? "open" : "xdg-open";
1524
- const args = p === "win32" ? ["/c", "start", "", url] : [url];
1525
- const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
1623
+ const cmd = p === "win32" ? "explorer" : p === "darwin" ? "open" : "xdg-open";
1624
+ const args = [url];
1625
+ const child = spawn2(cmd, args, { detached: true, stdio: "ignore" });
1526
1626
  child.unref();
1527
1627
  return true;
1528
1628
  } catch {
@@ -1543,11 +1643,11 @@ function reportError(err, context = {
1543
1643
  console.error("");
1544
1644
  const opened = tryOpenUrl(url);
1545
1645
  if (opened) {
1546
- console.error(" \u2192 Opened: " + url);
1646
+ console.error(` \u2192 Opened: ${url}`);
1547
1647
  } else {
1548
1648
  console.error(" Copy this URL to file a bug report:");
1549
1649
  console.error("");
1550
- console.error(" " + url);
1650
+ console.error(` ${url}`);
1551
1651
  }
1552
1652
  console.error("");
1553
1653
  return url;
@@ -2148,7 +2248,8 @@ function appendJSONL(filename, obj) {
2148
2248
  try {
2149
2249
  appendFileSync(
2150
2250
  join3(stateDir(), filename),
2151
- JSON.stringify(obj) + "\n",
2251
+ `${JSON.stringify(obj)}
2252
+ `,
2152
2253
  "utf-8"
2153
2254
  );
2154
2255
  } catch {
@@ -2299,7 +2400,7 @@ async function fetchReleases(fullName, etag, token) {
2299
2400
  Accept: "application/vnd.github+json"
2300
2401
  };
2301
2402
  if (etag) headers["If-None-Match"] = etag;
2302
- if (token) headers["Authorization"] = `Bearer ${token}`;
2403
+ if (token) headers.Authorization = `Bearer ${token}`;
2303
2404
  const res = await fetch(
2304
2405
  `https://api.github.com/repos/${fullName}/releases?per_page=${PER_REPO}`,
2305
2406
  { headers }
@@ -2536,7 +2637,7 @@ async function runInitWizard() {
2536
2637
  const themeStr = await ask(` Theme [${themes.join("/")}] (tokyo-night): `);
2537
2638
  const config = {
2538
2639
  defaultSort: ["best-match", "stars", "updated", "forks"].includes(sortStr) ? sortStr : "best-match",
2539
- defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr) || 50)),
2640
+ defaultLimit: Math.min(100, Math.max(10, parseInt(limitStr, 10) || 50)),
2540
2641
  theme: themes.includes(themeStr) ? themeStr : themes[0],
2541
2642
  cacheTtlSeconds: 300,
2542
2643
  defaultTab: "search"
@@ -2585,18 +2686,18 @@ function buildComparisonTable(repos) {
2585
2686
  const header = ["".padEnd(14), ...names.map((n) => n.padEnd(colW))];
2586
2687
  const sep = ["".padEnd(14, "\u2500"), ...names.map(() => "".padEnd(colW, "\u2500"))];
2587
2688
  const lines = [
2588
- "\u250C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u252C") + "\u2510",
2589
- "\u2502" + header.map((h) => ` ${h} `).join("\u2502") + "\u2502",
2590
- "\u251C" + sep.map((s) => "\u2500" + s + "\u2500").join("\u253C") + "\u2524"
2689
+ `\u250C${sep.map((s) => `\u2500${s}\u2500`).join("\u252C")}\u2510`,
2690
+ `\u2502${header.map((h) => ` ${h} `).join("\u2502")}\u2502`,
2691
+ `\u251C${sep.map((s) => `\u2500${s}\u2500`).join("\u253C")}\u2524`
2591
2692
  ];
2592
2693
  for (const row of rows) {
2593
2694
  const cells = [
2594
2695
  row.label.padEnd(14),
2595
2696
  ...row.values.map((v) => v.padEnd(colW))
2596
2697
  ];
2597
- lines.push("\u2502" + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
2698
+ lines.push(`\u2502${cells.map((c) => ` ${c} `).join("\u2502")}\u2502`);
2598
2699
  }
2599
- lines.push("\u2514" + sep.map((s) => "\u2500" + s + "\u2500").join("\u2534") + "\u2518");
2700
+ lines.push(`\u2514${sep.map((s) => `\u2500${s}\u2500`).join("\u2534")}\u2518`);
2600
2701
  return lines.join("\n");
2601
2702
  }
2602
2703
  function comparisonRepo(r) {
@@ -3074,7 +3175,8 @@ function deleteHistoryEntry(index) {
3074
3175
  );
3075
3176
  writeRaw(
3076
3177
  HISTORY_FILE,
3077
- remaining.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
3178
+ `${remaining.reverse().map((e) => JSON.stringify(e)).join("\n")}
3179
+ `
3078
3180
  );
3079
3181
  }
3080
3182
  function clearHistory() {
@@ -3086,7 +3188,8 @@ function rotateHistory() {
3086
3188
  const trimmed = entries.slice(0, MAX_ENTRIES);
3087
3189
  writeRaw(
3088
3190
  HISTORY_FILE,
3089
- trimmed.reverse().map((e) => JSON.stringify(e)).join("\n") + "\n"
3191
+ `${trimmed.reverse().map((e) => JSON.stringify(e)).join("\n")}
3192
+ `
3090
3193
  );
3091
3194
  }
3092
3195
 
@@ -3171,12 +3274,12 @@ function renderMarkdown(md, opts = {}) {
3171
3274
  if (codeLines.length === 0) return;
3172
3275
  const maxLen = Math.max(...codeLines.map((l) => l.length), codeLang.length);
3173
3276
  const barW = Math.min(maxLen + 4, width - 2);
3174
- out.push("\u250C" + "\u2500".repeat(barW) + "\u2510");
3175
- if (codeLang) out.push("\u2502 " + codeLang.padEnd(barW - 1) + "\u2502");
3277
+ out.push(`\u250C${"\u2500".repeat(barW)}\u2510`);
3278
+ if (codeLang) out.push(`\u2502 ${codeLang.padEnd(barW - 1)}\u2502`);
3176
3279
  for (const cl of codeLines) {
3177
- out.push("\u2502 " + cl.padEnd(barW - 1) + "\u2502");
3280
+ out.push(`\u2502 ${cl.padEnd(barW - 1)}\u2502`);
3178
3281
  }
3179
- out.push("\u2514" + "\u2500".repeat(barW) + "\u2518");
3282
+ out.push(`\u2514${"\u2500".repeat(barW)}\u2518`);
3180
3283
  out.push("");
3181
3284
  codeLines = [];
3182
3285
  codeLang = "";
@@ -3198,16 +3301,16 @@ function renderMarkdown(md, opts = {}) {
3198
3301
  const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
3199
3302
  if (totalW > width) {
3200
3303
  for (const row of dataRows) {
3201
- out.push(" " + row.map((c) => c.trim()).join(" \u2502 "));
3304
+ out.push(` ${row.map((c) => c.trim()).join(" \u2502 ")}`);
3202
3305
  }
3203
3306
  } else {
3204
- const sep = "\u250C" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510";
3205
- const mid = "\u251C" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u253C") + "\u2524";
3206
- const end = "\u2514" + colW.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518";
3307
+ const sep = `\u250C${colW.map((w) => "\u2500".repeat(w + 2)).join("\u252C")}\u2510`;
3308
+ const mid = `\u251C${colW.map((w) => "\u2500".repeat(w + 2)).join("\u253C")}\u2524`;
3309
+ const end = `\u2514${colW.map((w) => "\u2500".repeat(w + 2)).join("\u2534")}\u2518`;
3207
3310
  out.push(sep);
3208
3311
  dataRows.forEach((row, i) => {
3209
3312
  const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
3210
- out.push("\u2502 " + cells.map((c) => ` ${c} `).join("\u2502") + "\u2502");
3313
+ out.push(`\u2502 ${cells.map((c) => ` ${c} `).join("\u2502")}\u2502`);
3211
3314
  if (i === 0) out.push(mid);
3212
3315
  });
3213
3316
  out.push(end);
@@ -3238,7 +3341,7 @@ function renderMarkdown(md, opts = {}) {
3238
3341
  return parts;
3239
3342
  }
3240
3343
  for (let i = 0; i < lines.length; i++) {
3241
- let line = lines[i];
3344
+ const line = lines[i];
3242
3345
  if (/^```/.test(line)) {
3243
3346
  if (inCodeBlock) {
3244
3347
  flushCode();
@@ -3283,7 +3386,7 @@ function renderMarkdown(md, opts = {}) {
3283
3386
  if (bqMatch) {
3284
3387
  const inner = renderInline(bqMatch[1]);
3285
3388
  for (const wl of wrapLine(inner, width - 4)) {
3286
- out.push("\u2502 " + wl);
3389
+ out.push(`\u2502 ${wl}`);
3287
3390
  }
3288
3391
  continue;
3289
3392
  }
@@ -3291,7 +3394,7 @@ function renderMarkdown(md, opts = {}) {
3291
3394
  if (ulMatch) {
3292
3395
  const indent = ulMatch[1].length;
3293
3396
  const depth = Math.floor(indent / 2);
3294
- const prefix = " ".repeat(depth) + "\u2022 ";
3397
+ const prefix = `${" ".repeat(depth)}\u2022 `;
3295
3398
  const inner = renderInline(ulMatch[2]);
3296
3399
  for (const wl of wrapLine(inner, width - prefix.length)) {
3297
3400
  out.push(prefix + wl);
@@ -3302,7 +3405,7 @@ function renderMarkdown(md, opts = {}) {
3302
3405
  if (olMatch) {
3303
3406
  const indent = olMatch[1].length;
3304
3407
  const depth = Math.floor(indent / 2);
3305
- const prefix = " ".repeat(depth) + "\u25E6 ";
3408
+ const prefix = `${" ".repeat(depth)}\u25E6 `;
3306
3409
  const inner = renderInline(olMatch[2]);
3307
3410
  for (const wl of wrapLine(inner, width - prefix.length)) {
3308
3411
  out.push(prefix + wl);
@@ -3392,7 +3495,7 @@ var StatusManager = class {
3392
3495
  };
3393
3496
 
3394
3497
  // src/share.ts
3395
- import { spawn as spawn2 } from "child_process";
3498
+ import { spawn as spawn3 } from "child_process";
3396
3499
  function formatShare(repo, format2) {
3397
3500
  switch (format2) {
3398
3501
  case "markdown":
@@ -3410,7 +3513,7 @@ async function copyToClipboard(text) {
3410
3513
  const p = process.platform;
3411
3514
  const cmd = p === "win32" ? ["clip"] : p === "darwin" ? ["pbcopy"] : await findLinuxClipboard();
3412
3515
  if (!cmd) return false;
3413
- const proc = spawn2(cmd[0], cmd.slice(1), {
3516
+ const proc = spawn3(cmd[0], cmd.slice(1), {
3414
3517
  stdio: ["pipe", "ignore", "ignore"]
3415
3518
  });
3416
3519
  proc.stdin.write(text);
@@ -3473,9 +3576,10 @@ function nextTip() {
3473
3576
 
3474
3577
  // src/update-check.ts
3475
3578
  import { readFileSync as readFileSync5 } from "fs";
3476
- import { join as join5, dirname as dirname3 } from "path";
3579
+ import { join as join5, dirname as dirname3, posix } from "path";
3477
3580
  import { fileURLToPath as fileURLToPath3 } from "url";
3478
3581
  var REGISTRY_URL = "https://registry.npmjs.org/github-search-cli/latest";
3582
+ var PKG_NAME2 = "github-search-cli";
3479
3583
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3480
3584
  var REQUEST_TIMEOUT_MS = 5e3;
3481
3585
  var __dirname2 = dirname3(fileURLToPath3(import.meta.url));
@@ -3560,30 +3664,55 @@ function suppressUpdateNotices() {
3560
3664
  suppressed: true
3561
3665
  });
3562
3666
  }
3563
- async function performUpdate() {
3564
- const pkg = "github-search-cli";
3565
- const isBun = typeof process.versions !== "undefined" && "bun" in process.versions;
3566
- const cmd = isBun ? ["bun", "install", "-g", pkg] : ["npm", "install", "-g", pkg];
3667
+ function detectInstallLocation(pkgDir) {
3668
+ const norm = pkgDir.split("\\").join("/");
3669
+ const nodeModules = posix.dirname(norm);
3670
+ if (posix.basename(nodeModules) !== "node_modules") return null;
3671
+ const owner = posix.dirname(nodeModules);
3672
+ if (/(^|\/)\.bun(\/|$)/.test(owner)) return { channel: "bun" };
3673
+ const prefix = posix.basename(owner) === "lib" ? posix.dirname(owner) : owner;
3674
+ return { channel: "npm", prefix };
3675
+ }
3676
+ function buildUpdateCommand(loc) {
3677
+ if (loc.channel === "bun") return ["bun", "install", "-g", PKG_NAME2];
3678
+ const args = ["npm", "install", "-g"];
3679
+ if (loc.prefix) args.push("--prefix", loc.prefix);
3680
+ args.push(PKG_NAME2);
3681
+ return args;
3682
+ }
3683
+ function quoteWin32(arg) {
3684
+ return /\s/.test(arg) ? `"${arg}"` : arg;
3685
+ }
3686
+ async function performUpdate(location = detectInstallLocation(PKG_DIR2)) {
3687
+ if (!location) {
3688
+ debugLog(
3689
+ "Update skipped: the running install is not under a node_modules tree (compiled binary or dev checkout). Update it manually."
3690
+ );
3691
+ return false;
3692
+ }
3693
+ const argv = buildUpdateCommand(location);
3567
3694
  try {
3568
- if (isBun) {
3569
- const proc = Bun.spawn(cmd, {
3570
- stdout: "pipe",
3571
- stderr: "pipe"
3572
- });
3573
- await proc.exited;
3574
- if (proc.exitCode !== 0) {
3575
- const err = await proc.stderr.text();
3576
- debugLog(`Install failed (code ${proc.exitCode}): ${err}`);
3577
- }
3578
- return proc.exitCode === 0;
3579
- }
3580
- const { spawn: spawn3 } = await import("child_process");
3581
- return await new Promise((resolve) => {
3582
- const proc = spawn3(cmd[0], cmd.slice(1), { stdio: "ignore" });
3583
- proc.on("close", (code) => resolve(code === 0));
3584
- proc.on("error", () => resolve(false));
3695
+ const { spawn: spawn4 } = await import("child_process");
3696
+ const useShell = process.platform === "win32";
3697
+ const proc = spawn4(
3698
+ argv[0],
3699
+ useShell ? argv.slice(1).map(quoteWin32) : argv.slice(1),
3700
+ { stdio: ["ignore", "ignore", "pipe"], shell: useShell }
3701
+ );
3702
+ let stderr = "";
3703
+ proc.stderr?.on("data", (chunk) => {
3704
+ stderr += String(chunk);
3585
3705
  });
3586
- } catch {
3706
+ const code = await new Promise((resolve) => {
3707
+ proc.on("close", (c) => resolve(c));
3708
+ proc.on("error", () => resolve(-1));
3709
+ });
3710
+ if (code !== 0) {
3711
+ debugLog(`Install failed (code ${code}): ${stderr.trim()}`);
3712
+ }
3713
+ return code === 0;
3714
+ } catch (e) {
3715
+ debugLog(`Update failed: ${e instanceof Error ? e.message : String(e)}`);
3587
3716
  return false;
3588
3717
  }
3589
3718
  }
@@ -3603,7 +3732,7 @@ function fetchReleaseNotes(version) {
3603
3732
  try {
3604
3733
  const changelogPath = join5(PKG_DIR2, "CHANGELOG.md");
3605
3734
  const changelog = readFileSync5(changelogPath, "utf-8");
3606
- const header = "## [" + version + "]";
3735
+ const header = `## [${version}]`;
3607
3736
  const start = changelog.indexOf(header);
3608
3737
  if (start === -1) return null;
3609
3738
  const bodyStart = start + header.length;
@@ -3747,8 +3876,8 @@ async function launchBrowser(theme_override) {
3747
3876
  let currentPage = 1;
3748
3877
  let totalCount = 0;
3749
3878
  let deepDiveActive = false;
3750
- let compareList = [];
3751
- let quitArmed = false;
3879
+ const compareList = [];
3880
+ const _quitArmed = false;
3752
3881
  const header = new TextRenderable4(renderer, {
3753
3882
  content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
3754
3883
  bg: colors.bg,
@@ -4611,6 +4740,7 @@ async function launchBrowser(theme_override) {
4611
4740
  if (menuStack.length <= 1) return false;
4612
4741
  const prev = menuStack[menuStack.length - 2];
4613
4742
  const current = menuStack.pop();
4743
+ if (!current) return false;
4614
4744
  leaderSelect.options = prev.entries.map((e) => ({
4615
4745
  name: e.type === "category" ? ` ${e.icon} ${e.name} \u2192` : ` ${e.name}`,
4616
4746
  description: e.description,
@@ -4927,17 +5057,17 @@ async function launchBrowser(theme_override) {
4927
5057
  if (!cachedVersion) {
4928
5058
  cachedVersion = getVersion();
4929
5059
  }
4930
- const currentVersion2 = cachedVersion;
5060
+ const currentVersion = cachedVersion;
4931
5061
  if (!state.lastInstalledVersion) return;
4932
- if (state.lastInstalledVersion === currentVersion2) return;
4933
- const notes = fetchReleaseNotes(currentVersion2);
5062
+ if (state.lastInstalledVersion === currentVersion) return;
5063
+ const notes = fetchReleaseNotes(currentVersion);
4934
5064
  const notesWidth = Math.max(
4935
5065
  30,
4936
5066
  Math.floor((process.stdout.columns || 80) * 0.55) - 8
4937
5067
  );
4938
5068
  const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : " No release notes found for this version.";
4939
5069
  updateSelectedOption = 0;
4940
- updateText.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion2}
5070
+ updateText.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
4941
5071
 
4942
5072
  ${notesBlock}`;
4943
5073
  renderUpdateOptions();
@@ -4948,8 +5078,8 @@ ${notesBlock}`;
4948
5078
  if (cachedVersion === null) {
4949
5079
  cachedVersion = getVersion();
4950
5080
  }
4951
- const currentVersion2 = cachedVersion;
4952
- const latest = process.env.DEBUG_FORCE_UPDATE || await checkForUpdate(currentVersion2);
5081
+ const currentVersion = cachedVersion;
5082
+ const latest = process.env.DEBUG_FORCE_UPDATE || await checkForUpdate(currentVersion);
4953
5083
  markUpdateChecked(latest ?? void 0);
4954
5084
  if (latest) {
4955
5085
  updateSelectedOption = 0;
@@ -4960,20 +5090,20 @@ ${notesBlock}`;
4960
5090
  );
4961
5091
  const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : ` A new version is available
4962
5092
 
4963
- Current: ${currentVersion2}
5093
+ Current: ${currentVersion}
4964
5094
  Latest: ${latest}
4965
5095
 
4966
5096
  Run "npm install -g ghfind"`;
4967
5097
  updateText.content = ` A new version is available
4968
5098
 
4969
- Current: ${currentVersion2}
5099
+ Current: ${currentVersion}
4970
5100
  Latest: ${latest}
4971
5101
 
4972
5102
  ${notesBlock}`;
4973
5103
  renderUpdateOptions();
4974
5104
  showOverlay("update");
4975
5105
  } else {
4976
- debugLog(`No update available (current: ${currentVersion2})`);
5106
+ debugLog(`No update available (current: ${currentVersion})`);
4977
5107
  }
4978
5108
  }
4979
5109
  function openUrlIfSelected() {
@@ -5018,7 +5148,7 @@ ${notesBlock}`;
5018
5148
  }
5019
5149
  function saveCurrentSearch() {
5020
5150
  if (currentQueryInput) {
5021
- const name = currentQueryInput.length > 40 ? currentQueryInput.slice(0, 37) + "..." : currentQueryInput;
5151
+ const name = currentQueryInput.length > 40 ? `${currentQueryInput.slice(0, 37)}...` : currentQueryInput;
5022
5152
  saveSearch(
5023
5153
  name,
5024
5154
  currentQueryInput,
@@ -5235,13 +5365,11 @@ ${notesBlock}`;
5235
5365
  statusBar.content = ` ${text}`;
5236
5366
  renderer.requestRender();
5237
5367
  });
5238
- if (config.theme !== void 0 || true) {
5239
- setTimeout(() => {
5240
- if (currentOverlay === "none") {
5241
- setStatus(nextTip());
5242
- }
5243
- }, 2e3);
5244
- }
5368
+ setTimeout(() => {
5369
+ if (currentOverlay === "none") {
5370
+ setStatus(nextTip());
5371
+ }
5372
+ }, 2e3);
5245
5373
  function setStatus(msg) {
5246
5374
  statusMgr.set("idle", msg);
5247
5375
  }
@@ -5361,7 +5489,7 @@ ${notesBlock}`;
5361
5489
  if (idx >= 0) {
5362
5490
  const val = labelVals[idx];
5363
5491
  label = val >= 1e3 ? `${(val / 1e3).toFixed(1).replace(/\.0$/, "")}k` : String(val);
5364
- label = label.padStart(5) + " ";
5492
+ label = `${label.padStart(5)} `;
5365
5493
  }
5366
5494
  lines.push(`${label}\u2506${chars.join("")}`);
5367
5495
  }
@@ -5795,7 +5923,8 @@ ${pack.description ?? ""}`;
5795
5923
  if (key.name === "d") {
5796
5924
  const sel = historySelect.getSelectedOption();
5797
5925
  if (sel?.value) {
5798
- deleteHistoryEntry(sel.value.index);
5926
+ const idx = sel.value.index;
5927
+ if (idx !== void 0) deleteHistoryEntry(idx);
5799
5928
  refreshHistory();
5800
5929
  setStatus("Deleted history entry");
5801
5930
  }
@@ -5814,6 +5943,7 @@ ${pack.description ?? ""}`;
5814
5943
  if (sel?.value) {
5815
5944
  const entry = sel.value.entry;
5816
5945
  showOverlay("none");
5946
+ if (!entry) return;
5817
5947
  if (entry.mode === "trending") {
5818
5948
  if (entry.tab)
5819
5949
  trendingTab = entry.tab;
@@ -5832,7 +5962,9 @@ ${pack.description ?? ""}`;
5832
5962
  if (key.name === "d") {
5833
5963
  const sel = bookmarksSelect.getSelectedOption();
5834
5964
  if (sel?.value) {
5835
- removeBookmark(sel.value.repo.fullName);
5965
+ removeBookmark(
5966
+ sel.value.repo?.fullName ?? ""
5967
+ );
5836
5968
  refreshBookmarks();
5837
5969
  setStatus("Bookmark removed");
5838
5970
  }
@@ -5841,7 +5973,8 @@ ${pack.description ?? ""}`;
5841
5973
  }
5842
5974
  if (key.name === "enter" || key.name === "return") {
5843
5975
  const sel = bookmarksSelect.getSelectedOption();
5844
- if (sel?.value) openUrl(sel.value.repo.url);
5976
+ if (sel?.value)
5977
+ openUrl(sel.value.repo?.url ?? "");
5845
5978
  return;
5846
5979
  }
5847
5980
  return;
@@ -5850,7 +5983,7 @@ ${pack.description ?? ""}`;
5850
5983
  if (key.name === "d") {
5851
5984
  const sel = savedSelect.getSelectedOption();
5852
5985
  if (sel?.value) {
5853
- deleteSavedSearch(sel.value.name);
5986
+ deleteSavedSearch(sel.value.name ?? "");
5854
5987
  refreshSavedSearches();
5855
5988
  setStatus("Saved search deleted");
5856
5989
  }
@@ -5885,7 +6018,7 @@ ${pack.description ?? ""}`;
5885
6018
  if (sel?.value) {
5886
6019
  const topic = sel.value;
5887
6020
  showOverlay("none");
5888
- searchInput.value = `topic:${topic.name}`;
6021
+ searchInput.value = `topic:${topic.name ?? ""}`;
5889
6022
  showSearchMode();
5890
6023
  doSearch(`topic:${topic.name}`);
5891
6024
  }
@@ -6076,7 +6209,7 @@ ${pack.description ?? ""}`;
6076
6209
  }
6077
6210
  if (/^[1-5]$/.test(key.name)) {
6078
6211
  if (currentMode === "trending") {
6079
- const idx = parseInt(key.name) - 1;
6212
+ const idx = parseInt(key.name, 10) - 1;
6080
6213
  if (idx >= 0 && idx < TAB_NAMES.length) {
6081
6214
  trendingTab = TAB_NAMES[idx];
6082
6215
  loadTrending();
@@ -6120,14 +6253,14 @@ ${pack.description ?? ""}`;
6120
6253
  return;
6121
6254
  }
6122
6255
  });
6123
- const currentVersion = getVersion();
6124
- if (!session || !session.mode) {
6256
+ const _currentVersion = getVersion();
6257
+ if (!session?.mode) {
6125
6258
  showLanding(true);
6126
6259
  }
6127
6260
  checkForUpdateAndShow();
6128
6261
  checkPostUpgradePanel();
6129
6262
  renderer.start();
6130
- if (session && session.mode) {
6263
+ if (session?.mode) {
6131
6264
  if (currentMode === "trending") {
6132
6265
  loadTrending();
6133
6266
  } else {
@@ -6801,7 +6934,8 @@ async function processServerLine(line, state, config) {
6801
6934
  }
6802
6935
  }
6803
6936
  function writeStdout(line) {
6804
- process.stdout.write(line + "\n");
6937
+ process.stdout.write(`${line}
6938
+ `);
6805
6939
  }
6806
6940
  async function runMcpServer(options = {}) {
6807
6941
  const state = {
@@ -6820,8 +6954,9 @@ async function runMcpServer(options = {}) {
6820
6954
  let buffer = "";
6821
6955
  process.stdin.on("data", (chunk) => {
6822
6956
  buffer += chunk;
6823
- let idx;
6824
- while ((idx = buffer.indexOf("\n")) >= 0) {
6957
+ for (; ; ) {
6958
+ const idx = buffer.indexOf("\n");
6959
+ if (idx < 0) break;
6825
6960
  const line = buffer.slice(0, idx).trim();
6826
6961
  buffer = buffer.slice(idx + 1);
6827
6962
  if (!line) continue;
@@ -6879,7 +7014,7 @@ function parseArgs(args) {
6879
7014
  doctor: false,
6880
7015
  mcp: false
6881
7016
  };
6882
- let queryParts = [];
7017
+ const queryParts = [];
6883
7018
  for (let i = 0; i < args.length; i++) {
6884
7019
  const arg = args[i];
6885
7020
  switch (arg) {
@@ -6933,8 +7068,8 @@ function parseArgs(args) {
6933
7068
  flags.login = true;
6934
7069
  break;
6935
7070
  case "--limit": {
6936
- const parsed = parseInt(args[++i]);
6937
- if (isNaN(parsed) || parsed < 1 || parsed > 100) {
7071
+ const parsed = parseInt(args[++i], 10);
7072
+ if (Number.isNaN(parsed) || parsed < 1 || parsed > 100) {
6938
7073
  console.error(`Invalid limit: must be 1-100. Using default 50.`);
6939
7074
  flags.limit = 50;
6940
7075
  } else {
@@ -7174,7 +7309,12 @@ function printAgentSkill(name) {
7174
7309
  }
7175
7310
  async function runUserProfile(flags, outputFormat) {
7176
7311
  try {
7177
- const profile = await fetchUserProfile(flags.user, {
7312
+ const user = flags.user ?? "";
7313
+ if (!user) {
7314
+ console.error("Usage: ghfind user <name> [--json] [--token <t>]");
7315
+ process.exit(1);
7316
+ }
7317
+ const profile = await fetchUserProfile(user, {
7178
7318
  token: flags.token,
7179
7319
  limit: flags.limit
7180
7320
  });
@@ -7196,7 +7336,12 @@ async function runUserProfile(flags, outputFormat) {
7196
7336
  }
7197
7337
  async function runOrgProfile(flags, outputFormat) {
7198
7338
  try {
7199
- const profile = await fetchOrgProfile(flags.org, {
7339
+ const org = flags.org ?? "";
7340
+ if (!org) {
7341
+ console.error("Usage: ghfind org <name> [--json] [--token <t>]");
7342
+ process.exit(1);
7343
+ }
7344
+ const profile = await fetchOrgProfile(org, {
7200
7345
  token: flags.token,
7201
7346
  limit: flags.limit
7202
7347
  });
@@ -7327,7 +7472,7 @@ async function runReleases(flags, outputFormat) {
7327
7472
  }
7328
7473
  async function runNonInteractive(flags, outputFormat) {
7329
7474
  const handlers = {
7330
- trending: async (ctx2) => {
7475
+ trending: async (_ctx) => {
7331
7476
  const since = flags.since === "weekly" ? "weekly" : flags.since === "monthly" ? "monthly" : "daily";
7332
7477
  const trendingSearch = createTrendingSearch();
7333
7478
  let language;
@@ -7398,7 +7543,7 @@ async function runNonInteractive(flags, outputFormat) {
7398
7543
  token: flags.token
7399
7544
  });
7400
7545
  const repos = rankRepos(response.repos, flags.sort);
7401
- await pipeExec(repos, flags.pipe);
7546
+ if (flags.pipe) await pipeExec(repos, flags.pipe);
7402
7547
  },
7403
7548
  format: async (ctx2) => {
7404
7549
  const response = await ctx2.provider.search(ctx2.parsed, {