github-search-cli 0.6.1 → 0.7.0
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 +183 -188
- package/demo.svg +66 -0
- package/package.json +2 -1
- package/src/cli.ts +13 -3
- package/src/config.ts +1 -2
- package/src/errors.ts +2 -2
- package/src/explore.ts +17 -32
- package/src/export.ts +9 -1
- package/src/help.ts +120 -46
- package/src/init.ts +9 -1
- package/src/normalizer.ts +17 -17
- package/src/open-url.ts +17 -0
- package/src/output.ts +2 -1
- package/src/pipe.ts +2 -7
- package/src/provider.ts +32 -9
- package/src/query.ts +5 -7
- package/src/ranking.ts +5 -3
- package/src/session.ts +4 -2
- package/src/trending.ts +41 -65
- package/src/tui.ts +75 -24
- package/src/watch.ts +13 -1
package/README.md
CHANGED
|
@@ -1,188 +1,183 @@
|
|
|
1
|
-
# search-cli
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
search-cli
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
| `
|
|
62
|
-
|
|
|
63
|
-
| `
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
|
79
|
-
| `
|
|
80
|
-
|
|
|
81
|
-
| `
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
87
|
-
| `
|
|
88
|
-
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## License
|
|
187
|
-
|
|
188
|
-
MIT
|
|
1
|
+
# search-cli
|
|
2
|
+
|
|
3
|
+
> Browse, search, and trend GitHub repos from your terminal. No browser needed.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://raw.githubusercontent.com/codersdfs/search-cli/main/demo.svg" alt="search-cli demo" width="100%">
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Requires Bun (https://bun.sh)
|
|
15
|
+
npm install -g github-search-cli
|
|
16
|
+
|
|
17
|
+
# Launch the interactive TUI browser
|
|
18
|
+
search-cli
|
|
19
|
+
|
|
20
|
+
# Non-interactive search (pipe to jq, csvkit, etc.)
|
|
21
|
+
search-cli "language:Rust stars:>10000" --json | jq '.[].fullName'
|
|
22
|
+
search-cli "language:Zig" --count
|
|
23
|
+
search-cli --trending --json --since weekly
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## CLI modes
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
search-cli "query" --json # JSON array
|
|
30
|
+
search-cli "query" --csv # CSV
|
|
31
|
+
search-cli "query" --markdown # Markdown table
|
|
32
|
+
search-cli "query" --count # Just the number
|
|
33
|
+
search-cli "query" --format urls # One URL per line
|
|
34
|
+
search-cli "query" --pipe open # Open each in browser
|
|
35
|
+
search-cli "query" --pipe clone # Print clone commands
|
|
36
|
+
search-cli --trending --json # Trending as JSON
|
|
37
|
+
search-cli --watch "query" # Poll every 300s
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Shell completions
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
source <(search-cli --completion bash) # Bash
|
|
44
|
+
source <(search-cli --completion zsh) # Zsh
|
|
45
|
+
search-cli --completion fish | source # Fish
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `gh` extension
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
gh extension install ./gh-search-cli
|
|
52
|
+
gh search-cli "language:Rust"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
| Category | What you get |
|
|
60
|
+
|----------|-------------|
|
|
61
|
+
| **Search** | Free-text + qualifiers (`language:`, `stars:`, `topic:`), sort by stars/updated/forks, Tab auto-complete |
|
|
62
|
+
| **Trending** | Browse GitHub trending by day/week/month/year, filter by language |
|
|
63
|
+
| **Memory** | Persistent history (`Ctrl+R`), bookmarks (`b`/`B`), saved searches (`Ctrl+S`/`Ctrl+O`), session restore |
|
|
64
|
+
| **Deep-dive** | Per-repo detail: languages, top contributors, README preview (`d`) |
|
|
65
|
+
| **Compare** | Select 2+ repos and compare side-by-side (`c`/`C`) |
|
|
66
|
+
| **Explore** | Browse popular GitHub topics (`E`) |
|
|
67
|
+
| **Export** | JSON, CSV, Markdown, plain text (`Ctrl+E`) |
|
|
68
|
+
| **Shell** | Pipe-friendly output, `--watch` mode, shell completions, Docker |
|
|
69
|
+
| **Delight** | 3 themes (Tokyo Night, Dracula, Monokai), animated status bar, clipboard share, startup tips |
|
|
70
|
+
|
|
71
|
+
<details>
|
|
72
|
+
<summary><b>⌨️ Full keybindings</b></summary>
|
|
73
|
+
|
|
74
|
+
### Main view
|
|
75
|
+
|
|
76
|
+
| Key | Action |
|
|
77
|
+
|-----|--------|
|
|
78
|
+
| `/` | Focus search input |
|
|
79
|
+
| `Enter` | Execute search / open repo |
|
|
80
|
+
| `↑`/`↓` / `j`/`k` | Navigate results |
|
|
81
|
+
| `t` | Toggle search / trending |
|
|
82
|
+
| `s` | Cycle sort |
|
|
83
|
+
| `l` | Cycle limit |
|
|
84
|
+
| `r` | Refresh |
|
|
85
|
+
| `g` | Toggle activity graph |
|
|
86
|
+
| `d` | Toggle repo deep-dive |
|
|
87
|
+
| `o` | Open repo in browser |
|
|
88
|
+
| `b` / `B` | Toggle bookmark / open bookmarks |
|
|
89
|
+
| `c` / `C` | Toggle compare list / show comparison |
|
|
90
|
+
| `E` | Open topic explorer |
|
|
91
|
+
| `Tab` | Auto-complete qualifier |
|
|
92
|
+
| `?` / `Ctrl+H` | Help overlay |
|
|
93
|
+
| `q` | Quit |
|
|
94
|
+
|
|
95
|
+
### Overlays
|
|
96
|
+
|
|
97
|
+
| Key | Action |
|
|
98
|
+
|-----|--------|
|
|
99
|
+
| `Ctrl+R` | Search history |
|
|
100
|
+
| `Ctrl+S` | Save current search |
|
|
101
|
+
| `Ctrl+O` | Saved searches panel |
|
|
102
|
+
| `Ctrl+E` | Export results |
|
|
103
|
+
| `Ctrl+N` | Notifications |
|
|
104
|
+
| `Ctrl+P` | Share repo (copy to clipboard) |
|
|
105
|
+
| `PageDown` / `Ctrl+F` | Next page |
|
|
106
|
+
| `Esc` | Close overlay |
|
|
107
|
+
|
|
108
|
+
### Trending mode
|
|
109
|
+
|
|
110
|
+
| Key | Action |
|
|
111
|
+
|-----|--------|
|
|
112
|
+
| `1`–`5` | Switch tab (Today → Week → Month → Year → All) |
|
|
113
|
+
| `←`/`→` / `h`/`l` | Navigate tabs |
|
|
114
|
+
</details>
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Search syntax
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
language:Rust stars:>1000 topic:cli
|
|
122
|
+
"machine learning" language:Python stars:>5000
|
|
123
|
+
topic:cli -language:JavaScript
|
|
124
|
+
org:rust-lang language:Rust
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Supported qualifiers: `language:`, `stars:`, `fork:`, `archived:`, `topic:`, `user:`, `org:`, `repo:`, `updated:`, `pushed:`, `visibility:`, `license:`, `created:`, `size:`, `in:`. Prefix with `-` to exclude.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Configuration
|
|
132
|
+
|
|
133
|
+
`~/.config/search-cli/config.json` (created automatically):
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"defaultSort": "stars",
|
|
138
|
+
"defaultLimit": 50,
|
|
139
|
+
"theme": "tokyo-night",
|
|
140
|
+
"githubToken": "ghp_..."
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or run `search-cli init` for the interactive setup wizard.
|
|
145
|
+
|
|
146
|
+
Environment variables: `GITHUB_TOKEN`, `SEARCH_CLI_CONFIG`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Install from source
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/codersdfs/search-cli.git
|
|
154
|
+
cd search-cli
|
|
155
|
+
bun install
|
|
156
|
+
bun start # Launch TUI
|
|
157
|
+
bun test # 104 tests
|
|
158
|
+
bun run build # Compile binary → dist/
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Design
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
CLI → QueryBuilder → Provider → Normalizer → Ranking → TUI / stdout
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Layer | File | Job |
|
|
170
|
+
|-------|------|-----|
|
|
171
|
+
| Entry | `src/cli.ts` | Arg parsing, routes to TUI or stdout |
|
|
172
|
+
| TUI | `src/tui.ts` | Interactive browser (OpenTUI) |
|
|
173
|
+
| Query | `src/query.ts` | Parse keywords/qualifiers |
|
|
174
|
+
| Provider | `src/provider.ts` | GitHub REST API with caching + token rotation |
|
|
175
|
+
| Types | `src/types.ts` | Shared interfaces |
|
|
176
|
+
|
|
177
|
+
The `SearchProvider` interface is the extension point for alternative backends (GitLab, local index, AI retriever).
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT
|
package/demo.svg
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 803" width="900" height="803">
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
|
|
6
|
+
</style>
|
|
7
|
+
<filter id="shadow" x="-5%" y="-5%" width="115%" height="115%">
|
|
8
|
+
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#000" flood-opacity="0.4"/>
|
|
9
|
+
</filter>
|
|
10
|
+
</defs>
|
|
11
|
+
|
|
12
|
+
<rect width="100%" height="100%" fill="#0d0d1a"/>
|
|
13
|
+
|
|
14
|
+
<!-- Window -->
|
|
15
|
+
<rect x="12" y="10" width="876" height="783" fill="#1e1e2e" rx="8" filter="url(#shadow)"/>
|
|
16
|
+
<rect x="12" y="10" width="876" height="36" fill="#181825" rx="8"/>
|
|
17
|
+
<rect x="12" y="46" width="876" height="2" fill="#313244"/>
|
|
18
|
+
|
|
19
|
+
<!-- Traffic lights -->
|
|
20
|
+
<circle cx="24" cy="28" r="5" fill="#f38ba8"/>
|
|
21
|
+
<circle cx="42" cy="28" r="5" fill="#f9e2af"/>
|
|
22
|
+
<circle cx="60" cy="28" r="5" fill="#a6e3a1"/>
|
|
23
|
+
<text x="450" y="34" fill="#6c7086" font-family="'JetBrains Mono','Fira Code',monospace" font-size="13" text-anchor="middle">search-cli — terminal GitHub browser</text>
|
|
24
|
+
|
|
25
|
+
<g font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">
|
|
26
|
+
<text x="14" y="62" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">search-cli — GitHub repo browser [t]rending [/]search [g]raph [r]efresh [q]uit</text>
|
|
27
|
+
<rect x="14" y="69" width="860" height="54.5" fill="#1e1e2e" stroke="#89b4fa" stroke-width="1" rx="3"/>
|
|
28
|
+
<text x="22" y="88" fill="#89b4fa" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">> language:TypeScript stars:>1000</text>
|
|
29
|
+
<text x="14" y="150.2" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">sort: stars limit: 50 (s=cycle sort l=cycle limit)</text>
|
|
30
|
+
<rect x="14" y="159.3" width="432" height="378" fill="#1e1e2e" stroke="#45475a" stroke-width="1" rx="3"/>
|
|
31
|
+
<text x="22" y="177.5" fill="#89b4fa" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14"> Results </text>
|
|
32
|
+
<rect x="18" y="178.20000000000002" width="424" height="20" fill="#2d5bcf" rx="3"/>
|
|
33
|
+
<text x="24" y="198.5" fill="#ffffff" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">shadcn/ui ★ 78.5k</text>
|
|
34
|
+
<text x="24" y="221.6" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">vercel/next.js ★ 129k</text>
|
|
35
|
+
<text x="24" y="244.7" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">tailwindlabs/tailwindcss ★ 84.2k</text>
|
|
36
|
+
<text x="24" y="267.8" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">tauri-apps/tauri ★ 84.1k</text>
|
|
37
|
+
<text x="24" y="290.9" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">oven-sh/bun ★ 74.5k</text>
|
|
38
|
+
<text x="24" y="314" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">n8n-io/n8n ★ 54.4k</text>
|
|
39
|
+
<text x="24" y="337.1" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">astral-sh/ruff ★ 36.1k</text>
|
|
40
|
+
<text x="24" y="360.20000000000005" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">missive/emoji-mart ★ 8.8k</text>
|
|
41
|
+
<text x="24" y="383.3" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">sharkdp/bat ★ 50.3k</text>
|
|
42
|
+
<text x="24" y="406.4" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">BurntSushi/ripgrep ★ 48.8k</text>
|
|
43
|
+
<text x="24" y="429.5" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">jdx/mise ★ 14.3k</text>
|
|
44
|
+
<text x="24" y="452.6" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">nektos/act ★ 56.1k</text>
|
|
45
|
+
<text x="24" y="475.70000000000005" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">astral-sh/uv ★ 40.2k</text>
|
|
46
|
+
<text x="24" y="498.8" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">zed-industries/zed ★ 52k</text>
|
|
47
|
+
<rect x="450" y="159.3" width="432" height="378" fill="#1e1e2e" stroke="#45475a" stroke-width="1" rx="3"/>
|
|
48
|
+
<text x="456" y="177.5" fill="#a6e3a1" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14"> Details </text>
|
|
49
|
+
<text x="458" y="198.5" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Name shadcn/ui</text>
|
|
50
|
+
<text x="458" y="221.6" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Stars 78,500</text>
|
|
51
|
+
<text x="458" y="244.7" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Forks 4,200</text>
|
|
52
|
+
<text x="458" y="267.8" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Lang TypeScript</text>
|
|
53
|
+
<text x="458" y="290.9" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Updated 2026-07-12</text>
|
|
54
|
+
<text x="458" y="314" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Topics react, ui, components, shadcn</text>
|
|
55
|
+
<text x="458" y="360.20000000000005" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Beautifully designed components built with Radix UI and</text>
|
|
56
|
+
<text x="458" y="383.3" fill="#cdd6f4" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">Tailwind CSS.</text>
|
|
57
|
+
<text x="458" y="429.5" fill="#89b4fa" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">https://github.com/shadcn/ui</text>
|
|
58
|
+
<rect x="14" y="489" width="860" height="25" fill="#181825" rx="3"/>
|
|
59
|
+
<rect x="14" y="489" width="860" height="2" fill="#45475a"/>
|
|
60
|
+
<text x="18" y="521.9" fill="#6c7086" font-family="'JetBrains Mono','Fira Code','Cascadia Code',monospace" font-size="14">15,842 results (↑↓ nav Enter open o browser Ctrl+R history)</text>
|
|
61
|
+
<rect x="338.79999999999995" y="70" width="5.76" height="16" fill="#89b4fa" opacity="0.8">
|
|
62
|
+
<animate attributeName="opacity" values="0.8;0.1;0.8" dur="1s" repeatCount="indefinite"/>
|
|
63
|
+
</rect>
|
|
64
|
+
|
|
65
|
+
</g>
|
|
66
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-search-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Terminal-native GitHub browser — search, trending, bookmarks, history, deep-dive, compare, export, and more. All keyboard-driven, no browser needed.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"src/",
|
|
12
12
|
"completions/",
|
|
13
|
+
"demo.svg",
|
|
13
14
|
"LICENSE",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
package/src/cli.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { formatLines, pipeExec, type FormatLine } from "./pipe.ts";
|
|
|
23
23
|
import { runWatch } from "./watch.ts";
|
|
24
24
|
import { runInitWizard } from "./init.ts";
|
|
25
25
|
import { readFileSync } from "fs";
|
|
26
|
+
import { join } from "path";
|
|
26
27
|
import { launchBrowser } from "./tui.ts";
|
|
27
28
|
import type { SearchOptions } from "./types.ts";
|
|
28
29
|
|
|
@@ -79,7 +80,16 @@ function parseArgs(args: string[]): CLIFlags {
|
|
|
79
80
|
case "--version": case "-v": flags.version = true; break;
|
|
80
81
|
case "--help": case "-h": flags.help = true; break;
|
|
81
82
|
case "init": flags.init = true; break;
|
|
82
|
-
case "--limit":
|
|
83
|
+
case "--limit": {
|
|
84
|
+
const parsed = parseInt(args[++i]);
|
|
85
|
+
if (isNaN(parsed) || parsed < 1 || parsed > 100) {
|
|
86
|
+
console.error(`Invalid limit: must be 1-100. Using default 50.`);
|
|
87
|
+
flags.limit = 50;
|
|
88
|
+
} else {
|
|
89
|
+
flags.limit = parsed;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
83
93
|
case "--sort": flags.sort = args[++i] as SearchOptions["sort"]; break;
|
|
84
94
|
case "--token": flags.token = args[++i]; break;
|
|
85
95
|
case "--since": flags.since = args[++i]; break;
|
|
@@ -100,7 +110,7 @@ async function main() {
|
|
|
100
110
|
|
|
101
111
|
// Version
|
|
102
112
|
if (flags.version) {
|
|
103
|
-
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
113
|
+
const pkg = JSON.parse(readFileSync(join(import.meta.dir!, "..", "package.json"), "utf-8"));
|
|
104
114
|
console.log(`search-cli v${pkg.version}`);
|
|
105
115
|
return;
|
|
106
116
|
}
|
|
@@ -145,7 +155,7 @@ Options:
|
|
|
145
155
|
if (flags.completion) {
|
|
146
156
|
const shell = flags.completion;
|
|
147
157
|
try {
|
|
148
|
-
const content = readFileSync(`
|
|
158
|
+
const content = readFileSync(join(import.meta.dir!, "..", "completions", `search-cli.${shell}`), "utf-8");
|
|
149
159
|
console.log(content);
|
|
150
160
|
} catch {
|
|
151
161
|
console.error(`Completions not available for shell: ${shell}`);
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 2. $XDG_CONFIG_HOME/search-cli/config.json
|
|
7
7
|
* 3. ~/.config/search-cli/config.json
|
|
8
8
|
*/
|
|
9
|
+
import type { Config } from "./types.ts";
|
|
9
10
|
import { homedir } from "os";
|
|
10
11
|
import { join } from "path";
|
|
11
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -23,8 +24,6 @@ export function cacheDir(): string {
|
|
|
23
24
|
const base = xdg ? join(xdg, "search-cli") : join(homedir(), ".cache", "search-cli");
|
|
24
25
|
return base;
|
|
25
26
|
}
|
|
26
|
-
import type { Config } from "./types.ts";
|
|
27
|
-
|
|
28
27
|
const DEFAULTS: Config = {
|
|
29
28
|
defaultSort: "best-match",
|
|
30
29
|
defaultLimit: 50,
|
package/src/errors.ts
CHANGED
|
@@ -27,8 +27,8 @@ export class NetworkError extends SearchCliError {
|
|
|
27
27
|
export class RateLimitError extends SearchCliError {
|
|
28
28
|
constructor(public readonly hasToken: boolean, resetSeconds?: number) {
|
|
29
29
|
const msg = hasToken
|
|
30
|
-
? `⚠ API rate limit exceeded. Resets in ${resetSeconds ?? "?"}s. [r]etry [c]hange token`
|
|
31
|
-
: "⚠ Rate limited (
|
|
30
|
+
? `⚠ API rate limit exceeded (5,000/hr). Resets in ${resetSeconds ?? "?"}s. [r]etry [c]hange token`
|
|
31
|
+
: "⚠ Rate limited (60/hr). Set GITHUB_TOKEN for 5,000/hr. [r]etry";
|
|
32
32
|
super(msg, msg);
|
|
33
33
|
this.name = "RateLimitError";
|
|
34
34
|
}
|
package/src/explore.ts
CHANGED
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Topic explorer — browse popular GitHub topics.
|
|
3
3
|
*
|
|
4
|
-
* ponytail: fetch flat topic list, no hierarchy.
|
|
5
|
-
*
|
|
4
|
+
* ponytail: fetch flat topic list via search API, no hierarchy.
|
|
5
|
+
* Selecting a topic triggers a search.
|
|
6
6
|
*/
|
|
7
|
-
import { ParseError } from "./errors.ts";
|
|
8
|
-
|
|
9
7
|
export interface TopicItem {
|
|
10
8
|
name: string;
|
|
11
9
|
description: string;
|
|
12
10
|
repoCount: number;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
/** Fetch popular topics from
|
|
13
|
+
/** Fetch popular topics from GitHub search API.
|
|
14
|
+
* Requires the `mercy-preview` media type for the /search/topics endpoint.
|
|
15
|
+
*/
|
|
16
16
|
export async function fetchTopics(): Promise<TopicItem[]> {
|
|
17
|
-
const res = await fetch("https://github.com/topics", {
|
|
18
|
-
headers: {
|
|
17
|
+
const res = await fetch("https://api.github.com/search/topics?q=popular&per_page=30", {
|
|
18
|
+
headers: {
|
|
19
|
+
Accept: "application/vnd.github.mercy-preview+json",
|
|
20
|
+
"User-Agent": "search-cli",
|
|
21
|
+
},
|
|
19
22
|
});
|
|
20
|
-
if (!res.ok) throw new
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const block = cards[i];
|
|
28
|
-
|
|
29
|
-
const nameMatch = block.match(/href="\/topics\/([^"]+)"/);
|
|
30
|
-
if (!nameMatch) continue;
|
|
31
|
-
const name = decodeURIComponent(nameMatch[1]);
|
|
32
|
-
|
|
33
|
-
const descMatch = block.match(/<p[^>]*>([\s\S]*?)<\/p>/);
|
|
34
|
-
const description = descMatch
|
|
35
|
-
? descMatch[1].replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim()
|
|
36
|
-
: "";
|
|
37
|
-
|
|
38
|
-
const countMatch = block.match(/([\d,]+)\s+repositories/);
|
|
39
|
-
const repoCount = countMatch ? parseInt(countMatch[1].replace(/,/g, "")) : 0;
|
|
40
|
-
|
|
41
|
-
topics.push({ name, description, repoCount });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return topics.slice(0, 30);
|
|
23
|
+
if (!res.ok) throw new Error(`Topics API: HTTP ${res.status}`);
|
|
24
|
+
const data = await res.json() as { items?: Array<{ name: string; description?: string; score?: number }> };
|
|
25
|
+
return (data.items ?? []).map((item) => ({
|
|
26
|
+
name: item.name,
|
|
27
|
+
description: item.description || "",
|
|
28
|
+
repoCount: item.score || 0,
|
|
29
|
+
}));
|
|
45
30
|
}
|
package/src/export.ts
CHANGED
|
@@ -11,11 +11,19 @@ export function formatJson(repos: Repo[]): string {
|
|
|
11
11
|
return JSON.stringify(repos, null, 2);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function csvEscape(val: unknown): string {
|
|
15
|
+
const str = String(val ?? "");
|
|
16
|
+
if (str.includes('"') || str.includes(",") || str.includes("\n")) {
|
|
17
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
18
|
+
}
|
|
19
|
+
return str;
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
/** Format repos as CSV string. */
|
|
15
23
|
export function formatCsv(repos: Repo[]): string {
|
|
16
24
|
const header = "rank,full_name,stars,forks,language,url";
|
|
17
25
|
const rows = repos.map((r, i) =>
|
|
18
|
-
`${i + 1}
|
|
26
|
+
`${i + 1},${csvEscape(r.fullName)},${r.stars},${r.forks},${csvEscape(r.language ?? "")},${csvEscape(r.url)}`,
|
|
19
27
|
);
|
|
20
28
|
return [header, ...rows].join("\n");
|
|
21
29
|
}
|