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
@@ -4,7 +4,7 @@ import {
4
4
  loadConfig,
5
5
  saveConfig,
6
6
  stateDir
7
- } from "./chunk-IVUU2KGR.js";
7
+ } from "./chunk-GYIL4S5X.js";
8
8
  export {
9
9
  cacheDir,
10
10
  configPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-search-cli",
3
- "version": "9.4.2",
3
+ "version": "9.4.4",
4
4
  "description": "Search GitHub from your terminal — repo search, trending repos, npm package search, bookmarks, deep-dive, compare, export. Keyboard-driven TUI + pipeable CLI.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1",
@@ -34,8 +34,11 @@
34
34
  "test": "bun test",
35
35
  "dev": "bun run --watch src/tui.ts",
36
36
  "postinstall": "node scripts/postinstall.js",
37
- "format:check": "bunx prettier --check .",
38
- "format": "bunx prettier --write ."
37
+ "format:check": "prettier --check .",
38
+ "format": "prettier --write .",
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "bunx biome@2.5.13 check .",
41
+ "check": "prettier --check . && bun test"
39
42
  },
40
43
  "dependencies": {
41
44
  "@opentui/core": "^0.4.3"
@@ -1,29 +1,32 @@
1
- # What's New in ghfind 9.3.0
2
-
3
- Release tracking for bookmarked repos, plus a bookmarks page and toast notifications in the TUI.
4
-
5
- ### Added
6
-
7
- **Release tracker** — follow releases of repos you've bookmarked
8
- - `src/releases.ts` — checks each bookmarked repo against `GET /repos/{owner}/{repo}/releases?per_page=5`
9
- - Seen state is `lastSeenAt` per bookmark; "new" means published after that timestamp
10
- - ETag conditional requests + `GITHUB_TOKEN` when available to conserve rate budget
11
- - Prereleases shown and tagged `[pre]`; drafts never shown
12
- - `ghfind --releases` prints the new-release feed, then marks everything seen; `--releases --json` for machine output
13
- - Opening the Notifications panel (Space → System → Notifications) now runs a check in the background and files each new release as a `🚀` notification
14
-
15
- **Bookmarks page** — bookmarks are now a first-class mode, not an overlay
16
- - Reachable from the landing screen (`b`, or `Enter` on the new button) and from Space → Navigate → Bookmarks
17
- - `Enter` opens the repo in a browser, `d` removes, `Esc` / `←` returns to landing
18
- - `SessionState.mode` accepts `"bookmarks"`, so a restart restores you onto the page
19
-
20
- **Landing bookmarks button** — fourth selectable row below the three mode cards, with a live count (`4 saved` / `none yet`) refreshed on every visit
21
-
22
- **Toast popup** — top-right rounded card confirming bookmark changes; green border for bookmarked, yellow for unbookmarked, auto-hides after 2s
23
-
24
- **`b` keybinding** — toggles bookmark on the selected repo from the main view (previously only reachable via Space → Repo → Bookmark)
25
-
26
- ### Fixed
27
- - Landing screen vanished after the bookmarks page landed — `showLanding()` revealed `landingBox` before `hideMainContainer()`
28
- - `--releases` failed when no bookmarks existed — now returns empty feed instead
29
- - Toast stayed visible during TUI resize — now dismisses on `<Resize>`
1
+ # What's New in ghfind 9.3.0
2
+
3
+ Release tracking for bookmarked repos, plus a bookmarks page and toast notifications in the TUI.
4
+
5
+ ### Added
6
+
7
+ **Release tracker** — follow releases of repos you've bookmarked
8
+
9
+ - `src/releases.ts` — checks each bookmarked repo against `GET /repos/{owner}/{repo}/releases?per_page=5`
10
+ - Seen state is `lastSeenAt` per bookmark; "new" means published after that timestamp
11
+ - ETag conditional requests + `GITHUB_TOKEN` when available to conserve rate budget
12
+ - Prereleases shown and tagged `[pre]`; drafts never shown
13
+ - `ghfind --releases` prints the new-release feed, then marks everything seen; `--releases --json` for machine output
14
+ - Opening the Notifications panel (Space → System → Notifications) now runs a check in the background and files each new release as a `🚀` notification
15
+
16
+ **Bookmarks page** — bookmarks are now a first-class mode, not an overlay
17
+
18
+ - Reachable from the landing screen (`b`, or `Enter` on the new button) and from Space → Navigate → Bookmarks
19
+ - `Enter` opens the repo in a browser, `d` removes, `Esc` / `←` returns to landing
20
+ - `SessionState.mode` accepts `"bookmarks"`, so a restart restores you onto the page
21
+
22
+ **Landing bookmarks button** — fourth selectable row below the three mode cards, with a live count (`4 saved` / `none yet`) refreshed on every visit
23
+
24
+ **Toast popup** — top-right rounded card confirming bookmark changes; green border for bookmarked, yellow for unbookmarked, auto-hides after 2s
25
+
26
+ **`b` keybinding** — toggles bookmark on the selected repo from the main view (previously only reachable via Space → Repo → Bookmark)
27
+
28
+ ### Fixed
29
+
30
+ - Landing screen vanished after the bookmarks page landed — `showLanding()` revealed `landingBox` before `hideMainContainer()`
31
+ - `--releases` failed when no bookmarks existed — now returns empty feed instead
32
+ - Toast stayed visible during TUI resize — now dismisses on `<Resize>`
package/scripts/build.ts CHANGED
@@ -1,19 +1,34 @@
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
- });
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(
12
+ "npx",
13
+ [
14
+ "tsup",
15
+ "src/cli.ts",
16
+ "--format",
17
+ "esm",
18
+ "--target",
19
+ "node20",
20
+ "--out-dir",
21
+ "dist",
22
+ "--external",
23
+ "@opentui/core",
24
+ ],
25
+ { stdio: "inherit" },
26
+ );
27
+
28
+ console.log("✓ dist/cli.js (Node.js ESM bundle)");
29
+ }
30
+
31
+ main().catch((err) => {
32
+ console.error("Build failed:", err.message);
33
+ process.exit(1);
34
+ });
@@ -1,168 +1,218 @@
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);
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,
26
+ FH = 21,
27
+ PX = 14,
28
+ PY = 12,
29
+ WIN_H = 36;
30
+ const W = 90,
31
+ H = 35;
32
+ const SW = Math.round(W * FW + PX * 2 + 8);
33
+ const SH = Math.round(H * FH + PY * 2 + WIN_H + 8);
34
+
35
+ const esc = (s) =>
36
+ String(s)
37
+ .replace(/&/g, "&amp;")
38
+ .replace(/</g, "&lt;")
39
+ .replace(/>/g, "&gt;")
40
+ .replace(/"/g, "&quot;");
41
+
42
+ const repos = [
43
+ { fn: "shadcn/ui", s: 78500, l: "TypeScript" },
44
+ { fn: "vercel/next.js", s: 129000, l: "TypeScript" },
45
+ { fn: "tailwindlabs/tailwindcss", s: 84200, l: "CSS" },
46
+ { fn: "tauri-apps/tauri", s: 84100, l: "Rust" },
47
+ { fn: "oven-sh/bun", s: 74500, l: "Zig" },
48
+ { fn: "n8n-io/n8n", s: 54400, l: "TypeScript" },
49
+ { fn: "astral-sh/ruff", s: 36100, l: "Rust" },
50
+ { fn: "missive/emoji-mart", s: 8800, l: "TypeScript" },
51
+ { fn: "sharkdp/bat", s: 50300, l: "Rust" },
52
+ { fn: "BurntSushi/ripgrep", s: 48800, l: "Rust" },
53
+ { fn: "jdx/mise", s: 14300, l: "Rust" },
54
+ { fn: "nektos/act", s: 56100, l: "Go" },
55
+ { fn: "astral-sh/uv", s: 40200, l: "Rust" },
56
+ { fn: "zed-industries/zed", s: 52000, l: "Rust" },
57
+ ];
58
+
59
+ const f = (n) =>
60
+ n >= 1000 ? `★ ${(n / 1000).toFixed(1).replace(/\.0$/, "")}k` : `★ ${n}`;
61
+ const num = (n) => n.toLocaleString();
62
+
63
+ function tx(x, y, text, fill, cls = "") {
64
+ const c = cls ? ` class="${cls}"` : "";
65
+ 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`;
66
+ }
67
+
68
+ function rx(x, y, w, h, fill, stroke = "", r = 0) {
69
+ const s = stroke ? ` stroke="${stroke}" stroke-width="1"` : "";
70
+ const rc = r ? ` rx="${r}"` : "";
71
+ return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${fill}"${s}${rc}/>\n`;
72
+ }
73
+
74
+ let svg = "";
75
+
76
+ // ─── Header ──
77
+ svg += tx(
78
+ PX,
79
+ PY + WIN_H + 14,
80
+ "ghfind — GitHub repo browser [t]rending [/]search [g]raph [r]efresh [q]uit",
81
+ C.muted,
82
+ );
83
+
84
+ // ─── Search box (blue border, 3 lines height) ──
85
+ const sbY = PY + WIN_H + 1 * FH;
86
+ svg += rx(PX, sbY, W * FW - 4, FH * 2.5 + 2, C.bg, C.blue, 3);
87
+ svg += tx(PX + 8, sbY + FH - 2, "> language:TypeScript stars:>1000", C.blue);
88
+
89
+ // ─── Toolbar ──
90
+ svg += tx(
91
+ PX,
92
+ PY + WIN_H + 4.2 * FH + 14,
93
+ "sort: stars limit: 50 (s=cycle sort l=cycle limit)",
94
+ C.muted,
95
+ );
96
+
97
+ // ─── Body: Results (left) + Detail (right) ──
98
+ const bY = 5.5;
99
+ const paneW = (W * FW - 8) / 2;
100
+ const pH = 18;
101
+
102
+ // Left pane background
103
+ svg += rx(
104
+ PX,
105
+ PY + WIN_H + (bY - 0.2) * FH,
106
+ paneW + 4,
107
+ pH * FH,
108
+ C.bg,
109
+ C.border,
110
+ 3,
111
+ );
112
+ svg += tx(PX + 8, PY + WIN_H + bY * FH + 14, " Results ", C.blue);
113
+
114
+ // Results list
115
+ repos.forEach((r, i) => {
116
+ if (i >= 14) return;
117
+ const ry = bY + 1 + i * 1.1;
118
+ const isSel = i === 0;
119
+ if (isSel) {
120
+ svg += rx(
121
+ PX + 4,
122
+ PY + WIN_H + (ry - 0.3) * FH,
123
+ paneW - 4,
124
+ FH - 1,
125
+ C.selBg,
126
+ "",
127
+ 3,
128
+ );
129
+ }
130
+ const fgc = isSel ? C.selText : C.text;
131
+ const line = `${r.fn} ${f(r.s)}`;
132
+ svg += tx(PX + 10, PY + WIN_H + ry * FH + 14, line, fgc);
133
+ });
134
+
135
+ // Right pane background
136
+ svg += rx(
137
+ PX + paneW + 8,
138
+ PY + WIN_H + (bY - 0.2) * FH,
139
+ paneW + 4,
140
+ pH * FH,
141
+ C.bg,
142
+ C.border,
143
+ 3,
144
+ );
145
+ svg += tx(PX + paneW + 14, PY + WIN_H + bY * FH + 14, " Details ", C.green);
146
+
147
+ // Detail content (matches actual format from tui.ts line ~685)
148
+ const detail = [
149
+ `Name shadcn/ui`,
150
+ `Stars ${num(78500)}`,
151
+ `Forks ${num(4200)}`,
152
+ `Lang TypeScript`,
153
+ `Updated 2026-07-12`,
154
+ `Topics react, ui, components, shadcn`,
155
+ ``,
156
+ `Beautifully designed components built with Radix UI and`,
157
+ `Tailwind CSS.`,
158
+ ``,
159
+ `https://github.com/shadcn/ui`,
160
+ ];
161
+
162
+ detail.forEach((line, i) => {
163
+ if (line === "") return;
164
+ const ry = bY + 1 + i * 1.1;
165
+ let fgc = C.text;
166
+ if (i <= 5) fgc = C.muted;
167
+ if (line.startsWith("http")) fgc = C.blue;
168
+ svg += tx(PX + paneW + 16, PY + WIN_H + ry * FH + 14, line, fgc);
169
+ });
170
+
171
+ // ─── Bottom status bar ──
172
+ svg += rx(PX, PY + WIN_H + 21 * FH, W * FW - 4, FH + 4, C.surface, "", 3);
173
+ svg += rx(PX, PY + WIN_H + 21 * FH, W * FW - 4, 2, C.border);
174
+ svg += tx(
175
+ PX + 4,
176
+ PY + WIN_H + 21.9 * FH + 14,
177
+ "15,842 results (↑↓ nav Enter open o browser Ctrl+R history)",
178
+ C.muted,
179
+ );
180
+
181
+ // ─── Cursor blinking after query text ──
182
+ const queryText = "language:TypeScript stars:>1000";
183
+ const cursorX = PX + 8 + 2 * FW + queryText.length * FW;
184
+ svg += `<rect x="${cursorX}" y="${PY + WIN_H + 1 * FH + 1}" width="${FW * 0.6}" height="16" fill="${C.blue}" opacity="0.8">
185
+ <animate attributeName="opacity" values="0.8;0.1;0.8" dur="1s" repeatCount="indefinite"/>
186
+ </rect>\n`;
187
+
188
+ // ─── Assemble ──
189
+ const output = `<?xml version="1.0" encoding="UTF-8"?>
190
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${SW} ${SH}" width="${SW}" height="${SH}">
191
+ <defs>
192
+ <style>
193
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&amp;display=swap');
194
+ </style>
195
+ <filter id="shadow" x="-5%" y="-5%" width="115%" height="115%">
196
+ <feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.4"/>
197
+ </filter>
198
+ </defs>
199
+
200
+ <rect width="100%" height="100%" fill="#0d0d1a"/>
201
+
202
+ <!-- Window -->
203
+ <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)"/>
204
+ <rect x="${PX - 2}" y="${PY - 2}" width="${SW - PX * 2 + 4}" height="36" fill="${C.surface}" rx="8"/>
205
+ <rect x="${PX - 2}" y="${PY + 34}" width="${SW - PX * 2 + 4}" height="2" fill="#313244"/>
206
+
207
+ <!-- Traffic lights -->
208
+ <circle cx="${PX + 10}" cy="${PY + 16}" r="5" fill="#f38ba8"/>
209
+ <circle cx="${PX + 28}" cy="${PY + 16}" r="5" fill="#f9e2af"/>
210
+ <circle cx="${PX + 46}" cy="${PY + 16}" r="5" fill="#a6e3a1"/>
211
+ <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>
212
+
213
+ <g font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">
214
+ ${svg}
215
+ </g>
216
+ </svg>`;
217
+
218
+ process.stdout.write(output);