github-search-cli 9.3.0 → 9.4.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/CHANGELOG.md +238 -0
- package/README.md +28 -52
- package/bin/ghfind.js +55 -11
- package/dist/cli.js +787 -145
- package/package.json +76 -72
- package/release-notes/9.3.0.md +29 -0
- package/scripts/postinstall.js +82 -15
- package/src/bookmarks.ts +13 -0
- package/src/cli.ts +240 -51
- package/src/doctor.ts +287 -0
- package/src/error-report.ts +157 -0
- package/src/help.ts +17 -2
- package/src/landing.ts +104 -0
- package/src/notifications.ts +2 -1
- package/src/releases.ts +144 -0
- package/src/tips.ts +22 -7
- package/src/toast.ts +69 -0
- package/src/tui/overlays/help.ts +19 -0
- package/src/tui/overlays/registry.ts +52 -0
- package/src/tui.ts +38 -1
- package/src/types.ts +3 -1
- package/src/update-check.ts +53 -0
package/package.json
CHANGED
|
@@ -1,72 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "github-search-cli",
|
|
3
|
-
"version": "9.
|
|
4
|
-
"description": "Terminal-native GitHub search — search, trending, bookmarks, history, deep-dive, compare, export, and more. All keyboard-driven, no browser needed.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"packageManager": "bun@1",
|
|
7
|
-
"volta": {
|
|
8
|
-
"extensions": [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"@
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
},
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"github"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "github-search-cli",
|
|
3
|
+
"version": "9.4.1",
|
|
4
|
+
"description": "Terminal-native GitHub search — search, trending, bookmarks, history, deep-dive, compare, export, and more. All keyboard-driven, no browser needed.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "bun@1",
|
|
7
|
+
"volta": {
|
|
8
|
+
"extensions": [
|
|
9
|
+
".ts"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"ghfind": "bin/ghfind.js"
|
|
14
|
+
},
|
|
15
|
+
"bun": {
|
|
16
|
+
"smol": true
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"dist/",
|
|
21
|
+
"src/",
|
|
22
|
+
"scripts/",
|
|
23
|
+
"completions/",
|
|
24
|
+
"demo.svg",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"release-notes/",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "bun run src/cli.ts",
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"prepublishOnly": "bun run build",
|
|
34
|
+
"test": "bun test",
|
|
35
|
+
"dev": "bun run --watch src/tui.ts",
|
|
36
|
+
"postinstall": "node scripts/postinstall.js",
|
|
37
|
+
"format:check": "bunx prettier --check .",
|
|
38
|
+
"format": "bunx prettier --write ."
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@opentui/core": "^0.4.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/bun": "latest",
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"prettier": "^3.2.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.3.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/codersdfs/search-cli.git"
|
|
56
|
+
},
|
|
57
|
+
"bugs": {
|
|
58
|
+
"url": "https://github.com/codersdfs/search-cli/issues"
|
|
59
|
+
},
|
|
60
|
+
"homepage": "https://github.com/codersdfs/search-cli#readme",
|
|
61
|
+
"keywords": [
|
|
62
|
+
"github",
|
|
63
|
+
"cli",
|
|
64
|
+
"search",
|
|
65
|
+
"trending",
|
|
66
|
+
"terminal",
|
|
67
|
+
"tui",
|
|
68
|
+
"browser",
|
|
69
|
+
"developer-tools",
|
|
70
|
+
"opentui"
|
|
71
|
+
],
|
|
72
|
+
"license": "MIT",
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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>`
|
package/scripts/postinstall.js
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Postinstall script — downloads a Bun binary for ghfind's TUI.
|
|
4
4
|
*
|
|
5
|
-
* If vendor/bun or vendor/bun.exe already exists, this is a no-op.
|
|
6
|
-
* Otherwise, it downloads the correct Bun binary for the user's platform
|
|
5
|
+
* If a runnable vendor/bun or vendor/bun.exe already exists, this is a no-op.
|
|
6
|
+
* Otherwise, it downloads the correct Bun binary for the user's platform
|
|
7
|
+
* (including the right libc flavor for Linux: glibc vs musl).
|
|
7
8
|
* On failure, prints a warning and exits 0 so npm install still succeeds.
|
|
8
9
|
*/
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
chmodSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
} from "fs";
|
|
10
17
|
import { join, dirname } from "path";
|
|
11
18
|
import { fileURLToPath } from "url";
|
|
12
19
|
import { platform, arch } from "os";
|
|
20
|
+
import { spawnSync } from "child_process";
|
|
13
21
|
import { inflateRawSync } from "zlib";
|
|
14
22
|
|
|
15
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,9 +27,30 @@ const VENDOR_DIR = join(ROOT, "vendor");
|
|
|
19
27
|
const bunName = platform() === "win32" ? "bun.exe" : "bun";
|
|
20
28
|
const bunPath = join(VENDOR_DIR, bunName);
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Detect whether the current Linux system uses musl or glibc.
|
|
32
|
+
* Bun ships separate builds for each; musl binaries cannot run on glibc
|
|
33
|
+
* systems and vice versa.
|
|
34
|
+
*/
|
|
35
|
+
function libcFlavor() {
|
|
36
|
+
if (platform() !== "linux") return "glibc";
|
|
37
|
+
// Check /etc/os-release first (fast, no subprocess)
|
|
38
|
+
try {
|
|
39
|
+
const osRelease = readFileSync("/etc/os-release", "utf8");
|
|
40
|
+
if (/alpine|musl/i.test(osRelease)) return "musl";
|
|
41
|
+
} catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
// Fall back to ldd --version output
|
|
45
|
+
try {
|
|
46
|
+
const ldd = spawnSync("ldd", ["--version"], { encoding: "utf8" });
|
|
47
|
+
const out = `${ldd.stdout}${ldd.stderr}`;
|
|
48
|
+
if (/musl/i.test(out)) return "musl";
|
|
49
|
+
if (/glibc|GNU libc/i.test(out)) return "glibc";
|
|
50
|
+
} catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
return "glibc";
|
|
25
54
|
}
|
|
26
55
|
|
|
27
56
|
// Skip in CI/test environments
|
|
@@ -29,14 +58,38 @@ if (process.env.GHFIND_SKIP_BUN === "1" || process.env.CI === "true") {
|
|
|
29
58
|
process.exit(0);
|
|
30
59
|
}
|
|
31
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Returns true if an existing Bun binary is actually runnable.
|
|
63
|
+
* A binary of the wrong libc flavor (e.g. musl build on a glibc system)
|
|
64
|
+
* fails to launch with ENOENT because its dynamic loader is missing.
|
|
65
|
+
*/
|
|
66
|
+
function binaryRuns(path) {
|
|
67
|
+
try {
|
|
68
|
+
const res = spawnSync(path, ["--version"], { encoding: "utf8" });
|
|
69
|
+
return res.status === 0;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Already bundled and runnable — nothing to do
|
|
76
|
+
if (existsSync(bunPath) && binaryRuns(bunPath)) {
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
32
80
|
// Try to download
|
|
81
|
+
const linuxLibc = libcFlavor();
|
|
33
82
|
const map = {
|
|
34
83
|
"win32-x64": "bun-windows-x64.zip",
|
|
35
84
|
"win32-arm64": "bun-windows-aarch64.zip",
|
|
36
85
|
"darwin-x64": "bun-darwin-x64.zip",
|
|
37
86
|
"darwin-arm64": "bun-darwin-aarch64.zip",
|
|
38
|
-
"linux-x64":
|
|
39
|
-
|
|
87
|
+
"linux-x64":
|
|
88
|
+
linuxLibc === "musl" ? "bun-linux-x64-musl.zip" : "bun-linux-x64.zip",
|
|
89
|
+
"linux-arm64":
|
|
90
|
+
linuxLibc === "musl"
|
|
91
|
+
? "bun-linux-aarch64-musl.zip"
|
|
92
|
+
: "bun-linux-aarch64.zip",
|
|
40
93
|
};
|
|
41
94
|
|
|
42
95
|
const key = `${platform()}-${arch}`;
|
|
@@ -44,7 +97,9 @@ const assetName = map[key];
|
|
|
44
97
|
const version = "1.3.14";
|
|
45
98
|
|
|
46
99
|
if (!assetName) {
|
|
47
|
-
console.error(
|
|
100
|
+
console.error(
|
|
101
|
+
`[ghfind] Unsupported platform: ${key}. Install Bun manually: https://bun.sh`,
|
|
102
|
+
);
|
|
48
103
|
process.exit(0);
|
|
49
104
|
}
|
|
50
105
|
|
|
@@ -52,7 +107,9 @@ const url = `https://github.com/oven-sh/bun/releases/download/bun-v${version}/${
|
|
|
52
107
|
|
|
53
108
|
try {
|
|
54
109
|
mkdirSync(VENDOR_DIR, { recursive: true });
|
|
55
|
-
console.error(
|
|
110
|
+
console.error(
|
|
111
|
+
`[ghfind] Downloading Bun ${version} for ${key}${linuxLibc === "musl" ? " (musl)" : ""}...`,
|
|
112
|
+
);
|
|
56
113
|
|
|
57
114
|
const res = await fetch(url);
|
|
58
115
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -70,7 +127,9 @@ try {
|
|
|
70
127
|
const compSize = buffer.readUInt32LE(cdOffset + 20);
|
|
71
128
|
const compMethod = buffer.readUInt16LE(cdOffset + 10);
|
|
72
129
|
const nameStart = cdOffset + 46;
|
|
73
|
-
const fileName = buffer
|
|
130
|
+
const fileName = buffer
|
|
131
|
+
.subarray(nameStart, nameStart + nameLen)
|
|
132
|
+
.toString("utf8");
|
|
74
133
|
|
|
75
134
|
if (fileName.endsWith(bunName)) {
|
|
76
135
|
// Read the local file header to get the actual data offset
|
|
@@ -86,7 +145,9 @@ try {
|
|
|
86
145
|
fileData = buffer.subarray(dataOffset, dataOffset + compSize);
|
|
87
146
|
} else {
|
|
88
147
|
// Deflated
|
|
89
|
-
fileData = inflateRawSync(
|
|
148
|
+
fileData = inflateRawSync(
|
|
149
|
+
buffer.subarray(dataOffset, dataOffset + compSize),
|
|
150
|
+
);
|
|
90
151
|
}
|
|
91
152
|
|
|
92
153
|
writeFileSync(bunPath, fileData);
|
|
@@ -100,10 +161,16 @@ try {
|
|
|
100
161
|
|
|
101
162
|
throw new Error("Bun binary not found in archive");
|
|
102
163
|
} catch (err) {
|
|
103
|
-
console.warn(
|
|
104
|
-
|
|
164
|
+
console.warn(
|
|
165
|
+
`[ghfind] ⚠ Could not download Bun: ${err instanceof Error ? err.message : String(err)}`,
|
|
166
|
+
);
|
|
167
|
+
console.warn(
|
|
168
|
+
"[ghfind] The interactive TUI requires Bun. Install it manually:",
|
|
169
|
+
);
|
|
105
170
|
console.warn("[ghfind] curl -fsSL https://bun.sh/install | bash");
|
|
106
171
|
console.warn("[ghfind] # or: npm install -g bun");
|
|
107
|
-
console.warn(
|
|
172
|
+
console.warn(
|
|
173
|
+
"[ghfind] Non-interactive modes (--json, --csv, etc.) will still work with Node.js 20+.",
|
|
174
|
+
);
|
|
108
175
|
process.exit(0);
|
|
109
176
|
}
|
package/src/bookmarks.ts
CHANGED
|
@@ -54,3 +54,16 @@ export function searchBookmarks(query: string): Bookmark[] {
|
|
|
54
54
|
b.tags.some((t) => t.toLowerCase().includes(q)),
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
+
/** Read a repo's seen-marker (0 = never seen). */
|
|
58
|
+
export function getLastSeen(fullName: string): number {
|
|
59
|
+
return loadRaw().find((b) => b.repo.fullName === fullName)?.lastSeenAt ?? 0;
|
|
60
|
+
}
|
|
61
|
+
/** Mark a repo's releases as seen up to now. */
|
|
62
|
+
export function markSeen(fullName: string): void {
|
|
63
|
+
const bookmarks = loadRaw();
|
|
64
|
+
const bm = bookmarks.find((b) => b.repo.fullName === fullName);
|
|
65
|
+
if (bm) {
|
|
66
|
+
bm.lastSeenAt = Date.now();
|
|
67
|
+
saveRaw(bookmarks);
|
|
68
|
+
}
|
|
69
|
+
}
|