github-search-cli 0.8.1 → 0.8.2

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/README.md CHANGED
@@ -2,10 +2,18 @@
2
2
 
3
3
  > Search GitHub repos from your terminal. No browser needed.
4
4
 
5
+ <p align="center">
6
+ <a href="https://x.com/frankli23709971">
7
+ <img src="https://img.shields.io/badge/made%20by-%40frankli23709971-000000?logo=x&labelColor=000000&color=000000&link=https://x.com/frankli23709971" alt="Made by @frankli23709971">
8
+ </a>
9
+ </p>
10
+
5
11
  <p align="center">
6
12
  <img src="https://raw.githubusercontent.com/codersdfs/search-cli/main/demo.svg" alt="ghfind demo" width="100%">
7
13
  </p>
8
14
 
15
+ [![MIT License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
16
+
9
17
  ---
10
18
 
11
19
  ## Install
@@ -21,8 +29,7 @@ npm install -g github-search-cli
21
29
  > |---------|-----|-----------|
22
30
  > | Bundled Bun | ✅ | ✅ |
23
31
  > | System Bun | ✅ | ✅ |
24
- > | Node 20–22 | ✅ | ✅ |
25
- > | Node 23+ | ❌ | ✅ |
32
+ > | Node 20+ | ✅ | ✅ |
26
33
 
27
34
  ## Use
28
35
 
@@ -60,6 +67,18 @@ ghfind --completion fish | source
60
67
 
61
68
  ---
62
69
 
70
+ ## Changelog
71
+
72
+ ### v0.8.1
73
+ - Fixed postinstall to download Bun binary correctly on all platforms
74
+ - Fixed zip asset names to match Bun's current release naming
75
+ - Fixed zip parsing to use central directory instead of local file headers
76
+ - Removed `vendor/` from npm package (Bun binary downloaded at install time)
77
+ - Changed `postinstall` to use `node` instead of `bun run` (works without Bun pre-installed)
78
+ - TUI now works on Node.js 20+ via bundled Bun binary
79
+
80
+ ---
81
+
63
82
  ## Features
64
83
 
65
84
  <table>
package/bin/ghfind.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * ghfind — npm bin entry point.
4
4
  * Requires Node.js >= 20 or Bun.
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "github-search-cli",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Terminal-native GitHub search — search, trending, bookmarks, history, deep-dive, compare, export, and more. All keyboard-driven, no browser needed.",
5
5
  "type": "module",
6
+ "packageManager": "bun@1",
7
+ "volta": {
8
+ "extensions": [".ts"]
9
+ },
6
10
  "bin": {
7
11
  "ghfind": "bin/ghfind.js"
8
12
  },
13
+ "bun": {
14
+ "smol": true
15
+ },
9
16
  "files": [
10
17
  "bin/",
11
18
  "src/",
12
- "vendor/",
19
+ "scripts/",
13
20
  "completions/",
14
21
  "demo.svg",
15
22
  "LICENSE",
@@ -18,13 +25,12 @@
18
25
  "scripts": {
19
26
  "start": "bun run src/cli.ts",
20
27
  "build": "tsup",
21
- "prepublishOnly": "npm run build",
22
- "test": "vitest run",
23
- "test:bun": "bun test",
28
+ "prepublishOnly": "bun run build",
29
+ "test": "bun test",
24
30
  "dev": "bun run --watch src/tui.ts",
25
31
  "postinstall": "node scripts/postinstall.js",
26
- "format:check": "prettier --check .",
27
- "format": "prettier --write ."
32
+ "format:check": "bunx prettier --check .",
33
+ "format": "bunx prettier --write ."
28
34
  },
29
35
  "dependencies": {
30
36
  "@opentui/core": "^0.4.3"
@@ -34,12 +40,11 @@
34
40
  "@types/node": "^20.0.0",
35
41
  "prettier": "^3.2.0",
36
42
  "tsup": "^8.0.0",
37
- "tsx": "^4.0.0",
38
- "typescript": "^5.3.0",
39
- "vitest": "^1.2.0"
43
+ "typescript": "^5.3.0"
40
44
  },
41
45
  "engines": {
42
- "node": ">=20.0.0"
46
+ "node": ">=20.0.0",
47
+ "bun": ">=1.0.0"
43
48
  },
44
49
  "repository": {
45
50
  "type": "git",
@@ -0,0 +1,32 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ class SearchCli < Formula
5
+ desc "Interactive GitHub repository browser for the terminal"
6
+ homepage "https://github.com/frank/search-cli"
7
+ version "0.1.0"
8
+ license "MIT"
9
+
10
+ on_macos do
11
+ if Hardware::CPU.arm?
12
+ url "https://github.com/frank/search-cli/releases/download/v#{version}/search-cli-darwin-arm64"
13
+ sha256 "SHA256_PLACEHOLDER"
14
+ else
15
+ url "https://github.com/frank/search-cli/releases/download/v#{version}/search-cli-darwin-x64"
16
+ sha256 "SHA256_PLACEHOLDER"
17
+ end
18
+ end
19
+
20
+ on_linux do
21
+ url "https://github.com/frank/search-cli/releases/download/v#{version}/search-cli-linux-x64"
22
+ sha256 "SHA256_PLACEHOLDER"
23
+ end
24
+
25
+ def install
26
+ bin.install "search-cli"
27
+ end
28
+
29
+ test do
30
+ assert_match "search-cli", shell_output("#{bin}/search-cli --version")
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Build script — compile ghfind for distribution.
3
+ * Usage: npm run build
4
+ */
5
+ import { execFileSync } from "child_process";
6
+
7
+ async function main() {
8
+ console.log("Building ghfind...");
9
+
10
+ // ESM bundle via tsup
11
+ execFileSync("npx", ["tsup", "src/cli.ts", "--format", "esm", "--target", "node20", "--out-dir", "dist", "--external", "@opentui/core"], { stdio: "inherit" });
12
+
13
+ console.log("✓ dist/cli.js (Node.js ESM bundle)");
14
+ }
15
+
16
+ main().catch((err) => {
17
+ console.error("Build failed:", err.message);
18
+ process.exit(1);
19
+ });
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate an SVG screenshot of ghfind matching the actual TUI layout.
4
+ * Single frame with blinking cursor — honest and accurate.
5
+ *
6
+ * Usage: node scripts/generate-svg-demo.js > demo.svg
7
+ */
8
+
9
+ const C = {
10
+ bg: "#1e1e2e",
11
+ surface: "#181825",
12
+ text: "#cdd6f4",
13
+ muted: "#6c7086",
14
+ blue: "#89b4fa",
15
+ green: "#a6e3a1",
16
+ yellow: "#f9e2af",
17
+ red: "#f38ba8",
18
+ teal: "#94e2d5",
19
+ border: "#45475a",
20
+ selBg: "#2d5bcf",
21
+ selText: "#ffffff",
22
+ star: "#f9e2af",
23
+ };
24
+
25
+ const FW = 9.6, FH = 21, PX = 14, PY = 12, WIN_H = 36;
26
+ const W = 90, H = 35;
27
+ const SW = Math.round(W * FW + PX * 2 + 8);
28
+ const SH = Math.round(H * FH + PY * 2 + WIN_H + 8);
29
+
30
+ const esc = s => String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
31
+
32
+ const repos = [
33
+ {fn:"shadcn/ui",s:78500,l:"TypeScript"},
34
+ {fn:"vercel/next.js",s:129000,l:"TypeScript"},
35
+ {fn:"tailwindlabs/tailwindcss",s:84200,l:"CSS"},
36
+ {fn:"tauri-apps/tauri",s:84100,l:"Rust"},
37
+ {fn:"oven-sh/bun",s:74500,l:"Zig"},
38
+ {fn:"n8n-io/n8n",s:54400,l:"TypeScript"},
39
+ {fn:"astral-sh/ruff",s:36100,l:"Rust"},
40
+ {fn:"missive/emoji-mart",s:8800,l:"TypeScript"},
41
+ {fn:"sharkdp/bat",s:50300,l:"Rust"},
42
+ {fn:"BurntSushi/ripgrep",s:48800,l:"Rust"},
43
+ {fn:"jdx/mise",s:14300,l:"Rust"},
44
+ {fn:"nektos/act",s:56100,l:"Go"},
45
+ {fn:"astral-sh/uv",s:40200,l:"Rust"},
46
+ {fn:"zed-industries/zed",s:52000,l:"Rust"},
47
+ ];
48
+
49
+ const f = n => n >= 1000 ? `★ ${(n/1000).toFixed(1).replace(/\.0$/,"")}k` : `★ ${n}`;
50
+ const num = n => n.toLocaleString();
51
+
52
+ function tx(x, y, text, fill, cls="") {
53
+ const c = cls ? ` class="${cls}"` : "";
54
+ return `<text x="${x}" y="${y}" fill="${fill}" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14"${c}>${esc(text)}</text>\n`;
55
+ }
56
+
57
+ function rx(x, y, w, h, fill, stroke="", r=0) {
58
+ const s = stroke ? ` stroke="${stroke}" stroke-width="1"` : "";
59
+ const rc = r ? ` rx="${r}"` : "";
60
+ return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${fill}"${s}${rc}/>\n`;
61
+ }
62
+
63
+ let svg = "";
64
+
65
+ // ─── Header ──
66
+ svg += tx(PX, PY+WIN_H+14, "ghfind — GitHub repo browser [t]rending [/]search [g]raph [r]efresh [q]uit", C.muted);
67
+
68
+ // ─── Search box (blue border, 3 lines height) ──
69
+ const sbY = PY+WIN_H+1*FH;
70
+ svg += rx(PX, sbY, W*FW-4, FH*2.5+2, C.bg, C.blue, 3);
71
+ svg += tx(PX+8, sbY+FH-2, "> language:TypeScript stars:>1000", C.blue);
72
+
73
+ // ─── Toolbar ──
74
+ svg += tx(PX, PY+WIN_H+4.2*FH+14, "sort: stars limit: 50 (s=cycle sort l=cycle limit)", C.muted);
75
+
76
+ // ─── Body: Results (left) + Detail (right) ──
77
+ const bY = 5.5;
78
+ const paneW = (W*FW-8)/2;
79
+ const pH = 18;
80
+
81
+ // Left pane background
82
+ svg += rx(PX, PY+WIN_H+(bY-0.2)*FH, paneW+4, pH*FH, C.bg, C.border, 3);
83
+ svg += tx(PX+8, PY+WIN_H+(bY)*FH+14, " Results ", C.blue);
84
+
85
+ // Results list
86
+ repos.forEach((r, i) => {
87
+ if (i >= 14) return;
88
+ const ry = bY + 1 + i * 1.1;
89
+ const isSel = i === 0;
90
+ if (isSel) {
91
+ svg += rx(PX+4, PY+WIN_H+(ry-0.3)*FH, paneW-4, FH-1, C.selBg, "", 3);
92
+ }
93
+ const fgc = isSel ? C.selText : C.text;
94
+ const line = `${r.fn} ${f(r.s)}`;
95
+ svg += tx(PX+10, PY+WIN_H+ry*FH+14, line, fgc);
96
+ });
97
+
98
+ // Right pane background
99
+ svg += rx(PX+paneW+8, PY+WIN_H+(bY-0.2)*FH, paneW+4, pH*FH, C.bg, C.border, 3);
100
+ svg += tx(PX+paneW+14, PY+WIN_H+(bY)*FH+14, " Details ", C.green);
101
+
102
+ // Detail content (matches actual format from tui.ts line ~685)
103
+ const detail = [
104
+ `Name shadcn/ui`,
105
+ `Stars ${num(78500)}`,
106
+ `Forks ${num(4200)}`,
107
+ `Lang TypeScript`,
108
+ `Updated 2026-07-12`,
109
+ `Topics react, ui, components, shadcn`,
110
+ ``,
111
+ `Beautifully designed components built with Radix UI and`,
112
+ `Tailwind CSS.`,
113
+ ``,
114
+ `https://github.com/shadcn/ui`,
115
+ ];
116
+
117
+ detail.forEach((line, i) => {
118
+ if (line === "") return;
119
+ const ry = bY + 1 + i * 1.1;
120
+ let fgc = C.text;
121
+ if (i <= 5) fgc = C.muted;
122
+ if (line.startsWith("http")) fgc = C.blue;
123
+ svg += tx(PX+paneW+16, PY+WIN_H+ry*FH+14, line, fgc);
124
+ });
125
+
126
+ // ─── Bottom status bar ──
127
+ svg += rx(PX, PY+WIN_H+21*FH, W*FW-4, FH+4, C.surface, "", 3);
128
+ svg += rx(PX, PY+WIN_H+21*FH, W*FW-4, 2, C.border);
129
+ svg += tx(PX+4, PY+WIN_H+21.9*FH+14, "15,842 results (↑↓ nav Enter open o browser Ctrl+R history)", C.muted);
130
+
131
+ // ─── Cursor blinking after query text ──
132
+ const queryText = "language:TypeScript stars:>1000";
133
+ const cursorX = PX + 8 + 2 * FW + queryText.length * FW;
134
+ svg += `<rect x="${cursorX}" y="${PY + WIN_H + 1 * FH + 1}" width="${FW * 0.6}" height="16" fill="${C.blue}" opacity="0.8">
135
+ <animate attributeName="opacity" values="0.8;0.1;0.8" dur="1s" repeatCount="indefinite"/>
136
+ </rect>\n`;
137
+
138
+ // ─── Assemble ──
139
+ const output = `<?xml version="1.0" encoding="UTF-8"?>
140
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${SW} ${SH}" width="${SW}" height="${SH}">
141
+ <defs>
142
+ <style>
143
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&amp;display=swap');
144
+ </style>
145
+ <filter id="shadow" x="-5%" y="-5%" width="115%" height="115%">
146
+ <feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.4"/>
147
+ </filter>
148
+ </defs>
149
+
150
+ <rect width="100%" height="100%" fill="#0d0d1a"/>
151
+
152
+ <!-- Window -->
153
+ <rect x="${PX-2}" y="${PY-2}" width="${SW-PX*2+4}" height="${SH-PY*2+4}" fill="${C.bg}" rx="8" filter="url(#shadow)"/>
154
+ <rect x="${PX-2}" y="${PY-2}" width="${SW-PX*2+4}" height="36" fill="${C.surface}" rx="8"/>
155
+ <rect x="${PX-2}" y="${PY+34}" width="${SW-PX*2+4}" height="2" fill="#313244"/>
156
+
157
+ <!-- Traffic lights -->
158
+ <circle cx="${PX+10}" cy="${PY+16}" r="5" fill="#f38ba8"/>
159
+ <circle cx="${PX+28}" cy="${PY+16}" r="5" fill="#f9e2af"/>
160
+ <circle cx="${PX+46}" cy="${PY+16}" r="5" fill="#a6e3a1"/>
161
+ <text x="${SW/2}" y="${PY+22}" fill="${C.muted}" font-family="'JetBrains Mono','Fira Code',monospace" font-size="13" text-anchor="middle">ghfind — terminal GitHub browser</text>
162
+
163
+ <g font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">
164
+ ${svg}
165
+ </g>
166
+ </svg>`;
167
+
168
+ process.stdout.write(output);
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall script — ensures a Bun binary is available for ghfind.
4
+ *
5
+ * If vendor/bun or vendor/bun.exe already exists (bundled with the package),
6
+ * this is a no-op. Otherwise, it attempts to download the correct binary
7
+ * for the user's platform.
8
+ */
9
+ import { existsSync, mkdirSync, chmodSync, writeFileSync } from "fs";
10
+ import { join, dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import { platform, arch } from "os";
13
+ import { inflateRawSync } from "zlib";
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const ROOT = join(__dirname, "..");
17
+ const VENDOR_DIR = join(ROOT, "vendor");
18
+
19
+ const bunName = platform() === "win32" ? "bun.exe" : "bun";
20
+ const bunPath = join(VENDOR_DIR, bunName);
21
+
22
+ // Already bundled — nothing to do
23
+ if (existsSync(bunPath)) {
24
+ process.exit(0);
25
+ }
26
+
27
+ // Skip in CI/test environments
28
+ if (process.env.GHFIND_SKIP_BUN === "1" || process.env.CI === "true") {
29
+ process.exit(0);
30
+ }
31
+
32
+ // Try to download
33
+ const map = {
34
+ "win32-x64": "bun-windows-x64.zip",
35
+ "win32-arm64": "bun-windows-aarch64.zip",
36
+ "darwin-x64": "bun-darwin-x64.zip",
37
+ "darwin-arm64": "bun-darwin-aarch64.zip",
38
+ "linux-x64": "bun-linux-x64-musl.zip",
39
+ "linux-arm64": "bun-linux-aarch64-musl.zip",
40
+ };
41
+
42
+ const key = `${platform()}-${arch}`;
43
+ const assetName = map[key];
44
+ const version = "1.3.12";
45
+
46
+ if (!assetName) {
47
+ console.error(`[ghfind] Unsupported platform: ${key}. Install Bun manually: https://bun.sh`);
48
+ process.exit(0);
49
+ }
50
+
51
+ const url = `https://github.com/oven-sh/bun/releases/download/bun-v${version}/${assetName}`;
52
+
53
+ try {
54
+ mkdirSync(VENDOR_DIR, { recursive: true });
55
+ console.error(`[ghfind] Downloading Bun ${version} for ${key}...`);
56
+
57
+ const res = await fetch(url);
58
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
59
+
60
+ const buffer = Buffer.from(await res.arrayBuffer());
61
+
62
+ // Find the central directory file header signature (PK\x01\x02)
63
+ const cdSig = "PK\x01\x02";
64
+ let cdOffset = buffer.indexOf(cdSig, 0);
65
+ while (cdOffset !== -1) {
66
+ const nameLen = buffer.readUInt16LE(cdOffset + 28);
67
+ const extraLen = buffer.readUInt16LE(cdOffset + 30);
68
+ const commentLen = buffer.readUInt16LE(cdOffset + 32);
69
+ const localHeaderOffset = buffer.readUInt32LE(cdOffset + 42);
70
+ const compSize = buffer.readUInt32LE(cdOffset + 20);
71
+ const compMethod = buffer.readUInt16LE(cdOffset + 10);
72
+ const nameStart = cdOffset + 46;
73
+ const fileName = buffer.subarray(nameStart, nameStart + nameLen).toString("utf8");
74
+
75
+ if (fileName.endsWith(bunName)) {
76
+ // Read the local file header to get the actual data offset
77
+ const localSig = buffer.readUInt32LE(localHeaderOffset);
78
+ if (localSig !== 0x04034b50) throw new Error("Invalid local file header");
79
+ const localNameLen = buffer.readUInt16LE(localHeaderOffset + 26);
80
+ const localExtraLen = buffer.readUInt16LE(localHeaderOffset + 28);
81
+ const dataOffset = localHeaderOffset + 30 + localNameLen + localExtraLen;
82
+
83
+ let fileData;
84
+ if (compMethod === 0) {
85
+ // Stored (no compression)
86
+ fileData = buffer.subarray(dataOffset, dataOffset + compSize);
87
+ } else {
88
+ // Deflated
89
+ fileData = inflateRawSync(buffer.subarray(dataOffset, dataOffset + compSize));
90
+ }
91
+
92
+ writeFileSync(bunPath, fileData);
93
+ if (platform() !== "win32") chmodSync(bunPath, 0o755);
94
+ console.error(`[ghfind] Bun installed to vendor/${bunName}`);
95
+ process.exit(0);
96
+ }
97
+
98
+ cdOffset = buffer.indexOf(cdSig, cdOffset + 4);
99
+ }
100
+
101
+ throw new Error("Bun binary not found in archive");
102
+ } catch (err) {
103
+ console.error(`[ghfind] Failed to download Bun: ${err instanceof Error ? err.message : String(err)}`);
104
+ console.error("[ghfind] Install Bun manually: https://bun.sh/docs/install");
105
+ process.exit(0);
106
+ }
package/src/tui.ts CHANGED
@@ -90,11 +90,21 @@ export async function launchBrowser(): Promise<void> {
90
90
  try {
91
91
  renderer = await createCliRenderer();
92
92
  } catch (err) {
93
- console.error(
94
- "Failed to start the interactive browser. OpenTUI native backend unavailable.\n",
95
- `Reason: ${err instanceof Error ? err.message : String(err)}\n`,
96
- "Install Zig or use a platform with native OpenTUI support.",
97
- );
93
+ const isNode = process.versions.bun === undefined;
94
+ if (isNode) {
95
+ console.error("\n❌ The interactive TUI requires Bun runtime.\n");
96
+ console.error(" Install Bun: curl -fsSL https://bun.sh/install | bash");
97
+ console.error(" Then run: bunx ghfind");
98
+ console.error("\n Or use non-interactive mode (works on Node.js):");
99
+ console.error(" ghfind \"language:Rust\" --json | jq '.[].fullName'");
100
+ console.error(" ghfind --trending --count");
101
+ } else {
102
+ console.error(
103
+ "Failed to start the interactive browser. OpenTUI native backend unavailable.\n",
104
+ `Reason: ${err instanceof Error ? err.message : String(err)}\n`,
105
+ "Install Zig or use a platform with native OpenTUI support.",
106
+ );
107
+ }
98
108
  process.exit(1);
99
109
  return;
100
110
  }
@@ -1534,9 +1544,14 @@ export async function launchBrowser(): Promise<void> {
1534
1544
  return;
1535
1545
  }
1536
1546
 
1537
- // Space opens leader menu
1538
- if (key.name === "space") {
1539
- showLeaderMenu();
1547
+ // Space toggles leader menu (open if closed, close if open)
1548
+ // Skip when the search input is focused so spaces can be typed in queries
1549
+ if (key.name === "space" && !searchInput.focused) {
1550
+ if (currentOverlay === "leader") {
1551
+ showOverlay("none");
1552
+ } else {
1553
+ showLeaderMenu();
1554
+ }
1540
1555
  return;
1541
1556
  }
1542
1557
 
package/vendor/bun.exe DELETED
Binary file