github-search-cli 9.4.2 → 9.4.4

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +38 -4
  2. package/README.md +8 -3
  3. package/bin/ghfind.js +98 -89
  4. package/completions/ghfind.bash +1 -1
  5. package/completions/ghfind.fish +3 -1
  6. package/completions/ghfind.zsh +6 -0
  7. package/dist/{chunk-IVUU2KGR.js → chunk-GYIL4S5X.js} +4 -1
  8. package/dist/cli.js +761 -342
  9. package/dist/{config-GNZOQRTH.js → config-OCIPF2IF.js} +1 -1
  10. package/package.json +6 -3
  11. package/release-notes/9.3.0.md +32 -29
  12. package/scripts/build.ts +34 -19
  13. package/scripts/generate-svg-demo.js +218 -168
  14. package/scripts/postinstall.js +176 -176
  15. package/src/bookmarks.ts +69 -69
  16. package/src/cli.ts +17 -1
  17. package/src/compare.ts +69 -47
  18. package/src/config.ts +70 -65
  19. package/src/deepdive.ts +149 -118
  20. package/src/doctor.ts +287 -287
  21. package/src/errors.ts +62 -59
  22. package/src/explore.ts +34 -29
  23. package/src/export.ts +73 -63
  24. package/src/help.ts +157 -157
  25. package/src/history.ts +69 -55
  26. package/src/init.ts +60 -55
  27. package/src/landing.ts +106 -104
  28. package/src/login.ts +186 -0
  29. package/src/markdown-render.ts +1 -6
  30. package/src/notifications.ts +75 -59
  31. package/src/open-url.ts +16 -16
  32. package/src/org.ts +43 -15
  33. package/src/output.ts +153 -137
  34. package/src/package-browser.ts +317 -305
  35. package/src/package.ts +149 -137
  36. package/src/pipe.ts +60 -54
  37. package/src/releases.ts +182 -144
  38. package/src/saved-searches.ts +72 -50
  39. package/src/search-state.ts +283 -229
  40. package/src/search.ts +678 -653
  41. package/src/session.ts +26 -24
  42. package/src/share.ts +61 -54
  43. package/src/status.ts +68 -61
  44. package/src/storage.ts +136 -106
  45. package/src/themes.ts +259 -116
  46. package/src/tips.ts +42 -42
  47. package/src/toast.ts +70 -69
  48. package/src/trending-parser.ts +104 -0
  49. package/src/trending.ts +481 -462
  50. package/src/tui.ts +149 -153
  51. package/src/types.ts +160 -158
  52. package/src/watch.ts +95 -74
package/CHANGELOG.md CHANGED
@@ -5,8 +5,20 @@ All notable changes to `github-search-cli` (published as the `ghfind` binary).
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Published versions and dates mirror [npm](https://www.npmjs.com/package/github-search-cli?activeTab=versions).
6
6
 
7
7
  ---
8
+ ## [9.4.3] — 2026-09-13 (released)
8
9
 
9
- ## — 9.4.2
10
+ ### Added
11
+ - nothing added
12
+
13
+ ### Changed
14
+
15
+ - tui bug that local works but global doesn't
16
+
17
+ ### Fixed
18
+
19
+ - tui bug that local works but global doesn't
20
+
21
+ ## [9.4.2] — 2026-09-12 (released)
10
22
 
11
23
  ### Added
12
24
 
@@ -25,6 +37,21 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Publish
25
37
  - **README trust & comparison** — npm version / weekly downloads / CI /
26
38
  TypeScript / license badges; a "Why ghfind?" table vs `gh search` and
27
39
  `hub`; standalone-binary install section.
40
+ - **`ghfind login`** — stores a GitHub token for 5,000 req/hr rate limits.
41
+ Detects a token managed by the GitHub CLI (`~/.config/gh/hosts.yml`) and
42
+ offers to import it, or accepts a pasted token; warns if it doesn't look
43
+ like a GitHub credential (`ghp_` / `gho_` / `github_pat_`). Writes the same
44
+ config file as `ghfind init`.
45
+ - **Better failure hints** — the `bin/ghfind.js` entry now points broken
46
+ installs at `ghfind --doctor` instead of failing silently (previously a
47
+ missing `src/` + `dist/` exited 0 without a word).
48
+ - **Hardened trending parser** — `parseTrendingHtml` moved to its own module
49
+ (`src/trending-parser.ts`, no TUI/@opentui dependency). Layout changes that
50
+ previously produced an **empty trending list with no explanation** now throw
51
+ a friendly `ParseError` ("GitHub may have changed the layout. [r]etry").
52
+ New golden-test harness: `tests/fixtures/trending.html` is a live capture
53
+ of the page, and 11 tests pin the parser to it (including the login
54
+ `/login?return_to=…` href and `/owner/name/stargazers`-link false positives).
28
55
 
29
56
  ### Changed
30
57
 
@@ -43,12 +70,19 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Publish
43
70
  `package.json` via filesystem paths in `cli.ts`, `tui.ts`,
44
71
  `update-check.ts`, and `error-report.ts`, which do not exist inside a
45
72
  single-file executable. Version is now embedded at build time.
73
+ - **TUI crashed on boot with upgrade state (`bun start` → exit 1)** — the
74
+ update modal's `showOverlay("update")` called `updateSelect.focus()`, but
75
+ `updateSelect` was never declared (vestigial reference from the 9.4.0
76
+ refactor). Every launch where `update-state.json` recorded a version
77
+ upgrade threw `ReferenceError: updateSelect is not defined` before the
78
+ render loop started. The vestigial call is removed; the modal is driven by
79
+ `updateSelectedOption` + the global key handler, which now renders
80
+ correctly ("Updated ghfind X → Y" panel). Guarded by a source-grep test in
81
+ `tests/overlay-registry.test.ts`.
46
82
 
47
83
  ---
48
84
 
49
- ## [Unreleased] — 9.4.1
50
-
51
- ## [Unreleased] — 9.4.1
85
+ ## [Unreleased]
52
86
 
53
87
  ### Changed
54
88
 
package/README.md CHANGED
@@ -62,6 +62,7 @@ ghfind
62
62
  ghfind "language:Rust stars:>1000" --json | jq '.[].fullName'
63
63
  ghfind "language:Zig" --count
64
64
  ghfind --trending --json --since weekly
65
+ ghfind login # import the gh CLI token or paste one
65
66
  ```
66
67
 
67
68
  ### Non-interactive
@@ -190,7 +191,7 @@ Prefix with `-` to exclude. Supported: `language`, `stars`, `fork`, `archived`,
190
191
 
191
192
  ## Config
192
193
 
193
- `~/.config/ghfind/config.json` (auto-created, or run `ghfind init`):
194
+ `~/.config/ghfind/config.json` (auto-created, or run `ghfind init`; `ghfind login` will set your GitHub token):
194
195
 
195
196
  ```json
196
197
  {
@@ -214,14 +215,18 @@ cd ghfind
214
215
  # With Bun (recommended for TUI)
215
216
  bun install
216
217
  bun start # TUI
217
- bun test # 179 tests
218
+ bun test # 215 tests
219
+ bun run check # format check + tests — the green gate
218
220
  bun run build # build dist/
219
221
 
220
222
  # Or with npm
221
223
  npm install
222
224
  npm start # TUI (requires Node 20+)
223
- npm test # 179 tests
225
+ npm test # 215 tests
224
226
  npm run build # build dist/
227
+
228
+ > Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md) for commands,
229
+ > conventions, and the two tracked quality gates (`typecheck`, `lint`).
225
230
  ```
226
231
 
227
232
  ---
package/bin/ghfind.js CHANGED
@@ -1,89 +1,98 @@
1
- #!/usr/bin/env node
2
- /**
3
- * ghfind — npm bin entry point.
4
- * Requires Node.js >= 20 or Bun.
5
- *
6
- * Strategy 1: Use a Bun binary to run TypeScript source directly. Prefers the
7
- * bundled vendor/bun (downloaded by postinstall), then falls back to a
8
- * system-installed `bun` on PATH. Only runnable binaries are considered.
9
- * Strategy 2: Fall back to the bundled dist/cli.js with Node.js (works for
10
- * non-interactive modes).
11
- */
12
- import { existsSync } from "fs";
13
- import { spawnSync } from "child_process";
14
- import { join, dirname } from "path";
15
- import { fileURLToPath, pathToFileURL } from "url";
16
- import { platform } from "os";
17
- const __dirname = dirname(fileURLToPath(import.meta.url));
18
- const PKG_DIR = join(__dirname, "..");
19
-
20
- // Path to bundled Bun binary
21
- const BUN_BIN = join(
22
- PKG_DIR,
23
- "vendor",
24
- platform() === "win32" ? "bun.exe" : "bun",
25
- );
26
-
27
- // Path to bundled dist
28
- const distCli = join(PKG_DIR, "dist", "cli.js");
29
-
30
- // Path to source
31
- const srcCli = join(PKG_DIR, "src", "cli.ts");
32
-
33
- /** Returns "bun" if a system Bun is available on PATH, else undefined. */
34
- function systemBun() {
35
- try {
36
- const res = spawnSync("bun", ["--version"], {
37
- encoding: "utf8",
38
- timeout: 15000,
39
- });
40
- return res.status === 0 ? "bun" : undefined;
41
- } catch {
42
- return undefined;
43
- }
44
- }
45
-
46
- /** Returns true if the given Bun binary actually runs. */
47
- function bunRuns(bin) {
48
- try {
49
- const res = spawnSync(bin, ["--version"], {
50
- encoding: "utf8",
51
- timeout: 15000,
52
- });
53
- return res.status === 0;
54
- } catch {
55
- return false;
56
- }
57
- }
58
-
59
- // Strategy 1: Use Bun to run source directly (bundled binary first, then system bun)
60
- if (existsSync(srcCli)) {
61
- const candidates = [];
62
- if (existsSync(BUN_BIN)) candidates.push(BUN_BIN);
63
- const sysBun = systemBun();
64
- if (sysBun) candidates.push(sysBun);
65
-
66
- for (const bunBin of candidates) {
67
- if (!bunRuns(bunBin)) continue;
68
- const result = spawnSync(
69
- bunBin,
70
- ["run", srcCli, ...process.argv.slice(2)],
71
- {
72
- stdio: "inherit",
73
- env: { ...process.env, GHFIND_BUNDLED: "1" },
74
- },
75
- );
76
- process.exit(result.status ?? 0);
77
- }
78
- }
79
-
80
- // Strategy 2: Use bundled dist/cli.js with Node (non-interactive only on Node 23+)
81
- if (existsSync(distCli)) {
82
- try {
83
- await import(pathToFileURL(distCli).href);
84
- } catch (err) {
85
- console.error("Failed to start ghfind. Node.js >= 20 or Bun required.");
86
- console.error(err instanceof Error ? err.message : String(err));
87
- process.exit(1);
88
- }
89
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ghfind — npm bin entry point.
4
+ * Requires Node.js >= 20 or Bun.
5
+ *
6
+ * Strategy 1: Use a Bun binary to run TypeScript source directly. Prefers the
7
+ * bundled vendor/bun (downloaded by postinstall), then falls back to a
8
+ * system-installed `bun` on PATH. Only runnable binaries are considered.
9
+ * Strategy 2: Fall back to the bundled dist/cli.js with Node.js (works for
10
+ * non-interactive modes).
11
+ */
12
+ import { existsSync } from "fs";
13
+ import { spawnSync } from "child_process";
14
+ import { join, dirname } from "path";
15
+ import { fileURLToPath, pathToFileURL } from "url";
16
+ import { platform } from "os";
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const PKG_DIR = join(__dirname, "..");
19
+
20
+ // Path to bundled Bun binary
21
+ const BUN_BIN = join(
22
+ PKG_DIR,
23
+ "vendor",
24
+ platform() === "win32" ? "bun.exe" : "bun",
25
+ );
26
+
27
+ // Path to bundled dist
28
+ const distCli = join(PKG_DIR, "dist", "cli.js");
29
+
30
+ // Path to source
31
+ const srcCli = join(PKG_DIR, "src", "cli.ts");
32
+
33
+ /** Returns "bun" if a system Bun is available on PATH, else undefined. */
34
+ function systemBun() {
35
+ try {
36
+ const res = spawnSync("bun", ["--version"], {
37
+ encoding: "utf8",
38
+ timeout: 15000,
39
+ });
40
+ return res.status === 0 ? "bun" : undefined;
41
+ } catch {
42
+ return undefined;
43
+ }
44
+ }
45
+
46
+ /** Returns true if the given Bun binary actually runs. */
47
+ function bunRuns(bin) {
48
+ try {
49
+ const res = spawnSync(bin, ["--version"], {
50
+ encoding: "utf8",
51
+ timeout: 15000,
52
+ });
53
+ return res.status === 0;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ // Strategy 1: Use Bun to run source directly (bundled binary first, then system bun)
60
+ if (existsSync(srcCli)) {
61
+ const candidates = [];
62
+ if (existsSync(BUN_BIN)) candidates.push(BUN_BIN);
63
+ const sysBun = systemBun();
64
+ if (sysBun) candidates.push(sysBun);
65
+
66
+ for (const bunBin of candidates) {
67
+ if (!bunRuns(bunBin)) continue;
68
+ const result = spawnSync(
69
+ bunBin,
70
+ ["run", srcCli, ...process.argv.slice(2)],
71
+ {
72
+ stdio: "inherit",
73
+ env: { ...process.env, GHFIND_BUNDLED: "1" },
74
+ },
75
+ );
76
+ process.exit(result.status ?? 0);
77
+ }
78
+ }
79
+
80
+ // Strategy 2: Use bundled dist/cli.js with Node (non-interactive only on Node 23+)
81
+ if (existsSync(distCli)) {
82
+ try {
83
+ await import(pathToFileURL(distCli).href);
84
+ } catch (err) {
85
+ console.error("Failed to start ghfind. Node.js >= 20 or Bun required.");
86
+ console.error(
87
+ " Tip: run 'ghfind --doctor' for a full environment report.",
88
+ );
89
+ console.error(err instanceof Error ? err.message : String(err));
90
+ process.exit(1);
91
+ }
92
+ }
93
+
94
+ // Neither strategy produced a runnable CLI: report clearly instead of exiting 0.
95
+ console.error("ghfind: could not start — neither src/ nor dist/ is available.");
96
+ console.error(" Try reinstalling: npm install -g github-search-cli");
97
+ console.error(" Tip: run 'ghfind --doctor' for a full environment report.");
98
+ process.exit(1);
@@ -7,7 +7,7 @@ _ghfind() {
7
7
  cur="${COMP_WORDS[COMP_CWORD]}"
8
8
  prev="${COMP_WORDS[COMP_CWORD-1]}"
9
9
 
10
- opts="--json --csv --markdown --count --limit --sort --token --trending --since --pipe --format --registry --watch --interval --version --help --completion pkg"
10
+ opts="--json --csv --markdown --count --limit --sort --token --trending --since --pipe --format --registry --watch --interval --version --help --completion pkg login init doctor org"
11
11
 
12
12
  case "${prev}" in
13
13
  --sort)
@@ -18,4 +18,6 @@ complete -c ghfind -l watch -d "Watch mode"
18
18
  complete -c ghfind -l interval -d "Watch interval seconds" -r
19
19
  complete -c ghfind -l version -d "Print version"
20
20
  complete -c ghfind -l help -d "Print help"
21
- complete -c ghfind -l completion -d "Shell" -r -f -a "bash zsh fish"
21
+ complete -c ghfind -l completion -d "Shell" -r -f -a "bash zsh fish"
22
+
23
+ complete -c ghfind -f -a "login init doctor org pkg"
@@ -22,6 +22,12 @@ _ghfind() {
22
22
  '--version[Print version]'
23
23
  '--help[Print help]'
24
24
  '--completion[Shell]:(bash zsh fish)'
25
+
26
+ 'login[Import a GitHub token from the gh CLI or paste one]'
27
+ 'init[Run the setup wizard]'
28
+ 'doctor[Environment diagnostics]'
29
+ 'org[Org profile]:org name:'
30
+ 'pkg[Search packages]:query:'
25
31
  )
26
32
  _arguments "${opts[@]}" '*:query:'
27
33
  }
@@ -38,7 +38,10 @@ function loadConfig() {
38
38
  }
39
39
  function saveConfig(config) {
40
40
  const path = configPath();
41
- const dir = path.slice(0, Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")));
41
+ const dir = path.slice(
42
+ 0,
43
+ Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
44
+ );
42
45
  mkdirSync(dir, { recursive: true });
43
46
  writeFileSync(path, JSON.stringify(config, null, 2));
44
47
  }