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 CHANGED
@@ -1,188 +1,183 @@
1
- # search-cli
2
-
3
- > The terminal home for GitHub discovery — search, trending, bookmarks, and deep-dive repo exploration, all without touching a browser.
4
-
5
- ```bash
6
- npx github-search-cli # TUI browser
7
- search-cli "language:Rust stars:>1000" --json # CLI mode
8
- search-cli --version
9
- ```
10
-
11
- ---
12
-
13
- ## Features
14
-
15
- | Category | Features |
16
- |----------|----------|
17
- | **Search** | Free-text + qualifiers (`language:`, `stars:`, `topic:`, etc.), sort by stars/updated/forks, auto-complete on Tab |
18
- | **Trending** | Browse GitHub trending by day/week/month/year, language filter |
19
- | **Memory** | Persistent search history (`Ctrl+R`), bookmarks (`b`/`B`), saved searches (`Ctrl+S`/`Ctrl+O`), session restore |
20
- | **Deep-dive** | Per-repo detail: languages bar, top contributors, README preview (`d`) |
21
- | **Compare** | Select 2+ repos and compare side-by-side (`c`/`C`) |
22
- | **Explore** | Browse popular GitHub topics (`E`) |
23
- | **Export** | JSON, CSV, Markdown, plain text (`Ctrl+E`) |
24
- | **Shell** | Pipe-friendly `--json`/`--csv`/`--markdown`, `--format urls`, `--pipe open/clone`, `--watch`, shell completions |
25
- | **Delight** | 3 themes (Tokyo Night, Dracula, Monokai), animated status bar, notification center, clipboard share, startup tips |
26
-
27
- ---
28
-
29
- ## Quick start
30
-
31
- ```bash
32
- # Requires Bun (https://bun.sh)
33
- npm install -g github-search-cli
34
- # or: bun install -g github-search-cli
35
-
36
- # Launch the interactive TUI browser
37
- search-cli
38
-
39
- # Non-interactive search (pipe to jq, csvkit, etc.)
40
- search-cli "language:Rust stars:>10000" --json | jq '.[].fullName'
41
- search-cli "language:Zig" --count
42
- search-cli --trending --json --since weekly
43
- ```
44
-
45
- ---
46
-
47
- ## TUI keyboard shortcuts
48
-
49
- ### Main view
50
-
51
- | Key | Action |
52
- |-----|--------|
53
- | `/` | Focus search input |
54
- | `Enter` | Execute search / open repo |
55
- | `↑`/`↓` / `j`/`k` | Navigate results |
56
- | `t` | Toggle search / trending |
57
- | `s` | Cycle sort |
58
- | `l` | Cycle limit |
59
- | `r` | Refresh |
60
- | `g` | Toggle activity graph |
61
- | `d` | Toggle repo deep-dive |
62
- | `o` | Open repo in browser |
63
- | `b` / `B` | Toggle bookmark / open bookmarks |
64
- | `c` / `C` | Toggle compare list / show comparison |
65
- | `E` | Open topic explorer |
66
- | `Tab` | Auto-complete qualifier |
67
- | `?` / `Ctrl+H` | Help overlay |
68
- | `q` | Quit |
69
-
70
- ### Overlays
71
-
72
- | Key | Action |
73
- |-----|--------|
74
- | `Ctrl+R` | Search history |
75
- | `Ctrl+S` | Save current search |
76
- | `Ctrl+O` | Saved searches panel |
77
- | `Ctrl+E` | Export results |
78
- | `Ctrl+N` | Notifications |
79
- | `Ctrl+P` | Share repo (copy to clipboard) |
80
- | `PageDown` / `Ctrl+F` | Next page |
81
- | `Esc` | Close overlay |
82
-
83
- ### Trending mode
84
-
85
- | Key | Action |
86
- |-----|--------|
87
- | `1`–`5` | Switch tab (Today → Week → Month → Year → All) |
88
- | `←`/`→` / `h`/`l` | Navigate tabs |
89
-
90
- ---
91
-
92
- ## Search syntax
93
-
94
- ```
95
- language:Rust stars:>1000 topic:cli
96
- "machine learning" language:Python stars:>5000
97
- topic:cli -language:JavaScript
98
- org:rust-lang language:Rust
99
- ```
100
-
101
- Supported qualifiers: `language:`, `stars:`, `fork:`, `archived:`, `topic:`, `user:`, `org:`, `repo:`, `updated:`, `pushed:`, `visibility:`, `license:`, `created:`, `size:`, `in:`. Prefix with `-` to exclude.
102
-
103
- ---
104
-
105
- ## CLI modes (non-interactive)
106
-
107
- ```bash
108
- search-cli "query" --json # JSON array
109
- search-cli "query" --csv # CSV
110
- search-cli "query" --markdown # Markdown table
111
- search-cli "query" --count # Just the number
112
- search-cli "query" --format urls # One URL per line
113
- search-cli "query" --pipe open # Open each in browser
114
- search-cli "query" --pipe clone # Print clone commands
115
- search-cli --trending --json # Trending as JSON
116
- search-cli --watch "query" # Poll every 300s
117
- ```
118
-
119
- ### Shell completions
120
-
121
- ```bash
122
- source <(search-cli --completion bash) # Bash
123
- source <(search-cli --completion zsh) # Zsh
124
- search-cli --completion fish | source # Fish
125
- ```
126
-
127
- ### `gh` extension
128
-
129
- ```bash
130
- gh extension install ./gh-search-cli
131
- gh search-cli "language:Rust"
132
- ```
133
-
134
- ---
135
-
136
- ## Configuration
137
-
138
- `~/.config/search-cli/config.json` (created automatically):
139
-
140
- ```json
141
- {
142
- "defaultSort": "stars",
143
- "defaultLimit": 50,
144
- "theme": "tokyo-night",
145
- "githubToken": "ghp_..."
146
- }
147
- ```
148
-
149
- Or run `search-cli init` for the interactive setup wizard.
150
-
151
- Environment variables: `GITHUB_TOKEN`, `SEARCH_CLI_CONFIG`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`.
152
-
153
- ---
154
-
155
- ## Design
156
-
157
- ```
158
- CLI → QueryBuilder → Provider → Normalizer → Ranking → TUI / stdout
159
- ```
160
-
161
- | Layer | File | Job |
162
- |-------|------|-----|
163
- | Entry | `src/cli.ts` | Arg parsing, routes to TUI or stdout |
164
- | TUI | `src/tui.ts` | Interactive browser (OpenTUI) |
165
- | Query | `src/query.ts` | Parse keywords/qualifiers |
166
- | Provider | `src/provider.ts` | GitHub REST API with caching + token rotation |
167
- | Types | `src/types.ts` | Shared interfaces |
168
-
169
- The `SearchProvider` interface is the extension point for alternative backends (GitLab, local index, AI retriever).
170
-
171
- ---
172
-
173
- ## Install from source
174
-
175
- ```bash
176
- git clone https://github.com/codersdfs/search-cli.git
177
- cd search-cli
178
- bun install
179
- bun start # Launch TUI
180
- bun test # 104 tests
181
- bun run build # Compile binary → dist/
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&amp;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">&gt; language:TypeScript stars:&gt;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.6.1",
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": flags.limit = parseInt(args[++i]) || 50; break;
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(`completions/search-cli.${shell}`, "utf-8");
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 (10/min). Set GITHUB_TOKEN for 30/min, or wait. [r]etry";
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. Selecting a topic
5
- * triggers a search. Hierarchy adds ~60 lines for marginal benefit.
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 github.com/topics. */
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: { "User-Agent": "search-cli/1.0" },
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 ParseError("GitHub topics", `HTTP ${res.status}`);
21
- const html = await res.text();
22
-
23
- const topics: TopicItem[] = [];
24
- // Parse topic cards from the HTML
25
- const cards = html.split('<article class="border');
26
- for (let i = 1; i < cards.length; i++) {
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},"${r.fullName}",${r.stars},${r.forks},"${r.language ?? ""}","${r.url}"`,
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
  }